diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..b7f6a82349130010063d195f1b632547c8adf022
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+# Auto-generated VScode
+.vs
\ No newline at end of file
diff --git a/starter-kit/LICENSE b/starter-kit/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..ba3c9d72735ee7be588d66eb2b748b2f546b3913
--- /dev/null
+++ b/starter-kit/LICENSE
@@ -0,0 +1,22 @@
+MIT License
+
+Flutter Python Starter Kit
+Copyright (c) 2023 Maxim Saplin
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/starter-kit/bundle-python.sh b/starter-kit/bundle-python.sh
new file mode 100644
index 0000000000000000000000000000000000000000..df34ab74578e10b220c78987f7f57abb4c1250d9
--- /dev/null
+++ b/starter-kit/bundle-python.sh
@@ -0,0 +1,95 @@
+set -e # halt on any error
+#set -x
+
+flutterDir=""
+pythonDir=""
+exeName="server_py_flutter"
+nuitka=false
+
+while [[ $# -gt 0 ]]; do
+    case "$1" in
+        --proto)
+            proto="$2"
+            shift 2
+            ;;
+        --flutterDir)
+            flutterDir="$2"
+            shift 2
+            ;;
+        --pythonDir)
+            pythonDir="$2"
+            shift 2
+            ;;
+        --exeName)
+            exeName="$2"
+            shift 2
+            ;;
+        --nuitka)  # Set `nuitka` to `true` if there's a flag `--nuitka`
+            nuitka=true
+            shift
+            ;;
+        *)
+            shift
+            ;;
+    esac
+done
+
+flutterDir=$(realpath "$flutterDir" | sed 's/\/$//')
+pythonDir=$(realpath "$pythonDir" | sed 's/\/$//')
+workingDir=$(pwd)
+
+if [[ "$OSTYPE" == "msys" || "$OSTYPE" == "win32" ]]; then
+  PYTHON=python
+else
+  PYTHON=python3
+fi
+
+# Check the OS
+if [[ "$OSTYPE" == "msys" || "$OSTYPE" == "win32" ]]; then
+    exeNameFull="${exeName}_win"
+elif [[ "$OSTYPE" == "darwin"* ]]; then
+    exeNameFull="${exeName}_osx"
+else
+    exeNameFull="${exeName}_lnx"
+fi
+
+$PYTHON -m pip install -r $pythonDir/requirements.txt
+
+cd $pythonDir
+if [[ $nuitka == true ]]; then
+    export PYTHONPATH="./grpc_generated"
+    $PYTHON -m nuitka server.py --standalone --onefile --output-dir=./dist --output-filename="$exeNameFull"
+else
+    $PYTHON -m PyInstaller --onefile --noconfirm --clean --log-level=WARN --name="$exeNameFull" --paths="./grpc_generated" server.py
+fi
+cd $workingDir
+
+if [[ "$OSTYPE" == "msys" || "$OSTYPE" == "win32" ]]; then
+    exeNameFull="$exeNameFull.exe"
+fi
+
+mkdir -p $flutterDir/assets/
+cp $pythonDir/dist/$exeNameFull $flutterDir/assets/
+
+# Check if assets already exists in pubspec.yaml
+if ! grep -q "assets:" "$flutterDir"/pubspec.yaml; then
+    echo "  assets:" >> "$flutterDir"/pubspec.yaml
+    echo "    - assets/" >> "$flutterDir"/pubspec.yaml
+fi
+
+# Get current date time and create a string of the following format '2023_09_02_10_24_56'
+currentDateTime=$(date +"%Y_%m_%d_%H_%M_%S")
+
+# Update version in py_file_info.dart
+
+numLines=$(wc -l < "$flutterDir"/lib/grpc_generated/py_file_info.dart)
+lastLine=$((numLines - 2))
+head -n $lastLine "$flutterDir"/lib/grpc_generated/py_file_info.dart > "$flutterDir"/lib/grpc_generated/py_file_info_temp.dart
+mv "$flutterDir"/lib/grpc_generated/py_file_info_temp.dart "$flutterDir"/lib/grpc_generated/py_file_info.dart
+
+echo "const exeFileName = '$exeName';" >> "$flutterDir"/lib/grpc_generated/py_file_info.dart
+echo "const currentFileVersionFromAssets = '$currentDateTime';" >> "$flutterDir"/lib/grpc_generated/py_file_info.dart
+
+GREEN='\033[0;32m'
+NC='\033[0m'
+echo -e "\n${GREEN}Python built and put to $flutterDir/assets/$exeNameFull${NC}"
\ No newline at end of file
diff --git a/starter-kit/prepare-sources.sh b/starter-kit/prepare-sources.sh
new file mode 100644
index 0000000000000000000000000000000000000000..f966b902b49acf21c6418448c310e2e1cc62f25f
--- /dev/null
+++ b/starter-kit/prepare-sources.sh
@@ -0,0 +1,219 @@
+#!/bin/bash
+set -e # halt on any error
+#set -x
+
+proto=""
+flutterDir=""
+pythonDir=""
+exeName="server_py_flutter"
+
+# Loop through command line arguments
+
+while [[ $# -gt 0 ]]; do
+    case "$1" in
+        --proto)
+            proto="$2"
+            shift 2
+            ;;
+        --flutterDir)
+            flutterDir="$2"
+            shift 2
+            ;;
+        --pythonDir)
+            pythonDir="$2"
+            shift 2
+            ;;
+        --exeName)
+            exeName="$2"
+            shift 2
+            ;;
+        *)
+            shift
+            ;;
+    esac
+done
+
+if [[ -z "$proto" ]]; then
+    echo "Error: Missing required parameter '--proto'"
+    exit 1
+fi
+
+if [[ ! -f "$proto" ]]; then
+    echo "Error: Protofile '$proto' does not exist"
+    exit 1
+fi
+
+if [[ -z "$flutterDir" ]]; then
+    echo "Error: Missing required parameter '--flutterDir'"
+    exit 1
+fi
+
+if [[ -z "$pythonDir" ]]; then
+    echo "Error: Missing required parameter '--pythonDir'"
+    exit 1
+fi
+
+mkdir -p $flutterDir
+mkdir -p $pythonDir
+
+# Convert flutterDir and pythonDir to absolute paths
+flutterDir=$(realpath "$flutterDir" | sed 's/\/$//')
+pythonDir=$(realpath "$pythonDir" | sed 's/\/$//')
+scriptDir=$(dirname "$(realpath "$0")")
+workingDir=$(pwd)
+protoDir=$(dirname "$proto" | sed 's/\/$//')
+grpcGeneratedDir=$flutterDir/lib/grpc_generated
+protoFile=$(basename "$proto")
+
+serviceName=$(basename "$proto" .proto)
+
+# Set the Python interpreter name
+if [[ "$OSTYPE" == "msys" || "$OSTYPE" == "win32" ]]; then
+  PYTHON=python
+  grpcGeneratedDir=$(cygpath -w "$grpcGeneratedDir")
+else
+  PYTHON=python3
+fi
+
+
+flagFile="$protoDir/.starterDependenciesInstalled"
+
+if [ ! -f "$flagFile" ]; then
+    echo "Initializing dependencies"
+    echo "$OSTYPE"
+    # Prepare Dart/Flutter
+    # Update the installation command for different operating systems
+
+    if [[ "$OSTYPE" == "darwin"* ]]; then
+        # macOS
+        brew install protobuf
+    elif [[ "$OSTYPE" == "linux-gnu"* ]]; then
+        # Linux
+        sudo apt install protobuf-compiler
+        sudo apt install python3-pip
+    elif [[ "$OSTYPE" == "msys" || "$OSTYPE" == "win32" ]]; then
+        # Windows
+        choco install protoc
+    else
+        echo "Error: Unsupported operating system"
+        exit 1
+    fi
+
+
+    # Prepare Dart/Flutter
+    flutter pub global activate protoc_plugin
+
+    # Prepare Python dependencies
+    # pip3 install -r requirements.txt
+    $PYTHON -m pip install grpcio
+    $PYTHON -m pip install grpcio-tools
+    $PYTHON -m pip install tinyaes
+    $PYTHON -m pip install pyinstaller
+    $PYTHON -m pip install nuitka
+    touch "$flagFile"
+fi
+
+export PATH="$PATH":"$HOME/.pub-cache/bin" # make Dart's protoc_plugin available
+
+# Print the file name with extension
+echo "$fileNameWithExtension"
+
+# Generate Dart code
+mkdir -p $grpcGeneratedDir
+cd $protoDir # changing dir to avoid created nexted folders in --dart_out beacause of implicitly following grpc namespaces
+protoc --dart_out=grpc:"$grpcGeneratedDir" $protoFile
+echo "$(pwd)"
+cd $workingDir
+
+cd $flutterDir
+flutter pub add grpc || echo "Can't add `grpc` to Flutter project, continuing..."
+flutter pub add protobuf || echo "Can't add `protobuf` to Flutter project, continuing..."
+flutter pub add path_provider || echo "Can't add 'path_provider' to Flutter project, continuing..."
+flutter pub add path || echo "Can't add 'path' to Flutter project, continuing..."
+
+# macOS, update entitlements files and disable sandbox
+entitlements_file_1="macos/Runner/DebugProfile.entitlements"
+entitlements_file_2="macos/Runner/Release.entitlements"
+
+if [ -f "$entitlements_file_1" ]; then
+    # H;1h;$!d;x; - this part enables whole file processing (rather than line-by-line)
+    entitlements_content=$(echo "$entitlements_content" | sed 'H;1h;$!d;x; s/<key>com\.apple\.security\.app-sandbox<\/key>[[:space:]]*<true\/>/<key>com.apple.security.app-sandbox<\/key>\n\t<false\/>/' "$entitlements_file_1")
+    echo "$entitlements_content" > "$entitlements_file_1"
+fi
+
+if [ -f "$entitlements_file_2" ]; then
+    entitlements_content=$(echo "$entitlements_content" | sed 'H;1h;$!d;x; s/<key>com\.apple\.security\.app-sandbox<\/key>[[:space:]]*<true\/>/<key>com.apple.security.app-sandbox<\/key>\n\t<false\/>/' "$entitlements_file_2")
+    echo "$entitlements_content" > "$entitlements_file_2"
+fi
+
+# Dart clients
+if [[ ! -f "$grpcGeneratedDir/client.dart" ]]; then
+    cp "$scriptDir/templates/client.dart" "$grpcGeneratedDir/client.dart"
+fi
+if [[ ! -f "$grpcGeneratedDir/client_native.dart" ]]; then
+    cp "$scriptDir/templates/client_native.dart" "$grpcGeneratedDir/client_native.dart"
+fi
+if [[ ! -f "$grpcGeneratedDir/client_web.dart" ]]; then
+    cp "$scriptDir/templates/client_web.dart" "$grpcGeneratedDir/client_web.dart"
+fi
+
+if [ ! -f "$grpcGeneratedDir/init_py.dart" ]; then
+    cp "$scriptDir/templates/init_py.dart" "$grpcGeneratedDir/init_py.dart"
+fi
+if [ ! -f "$grpcGeneratedDir/init_py_native.dart" ]; then
+    cp "$scriptDir/templates/init_py_native.dart" "$grpcGeneratedDir/init_py_native.dart"
+fi
+if [ ! -f "$grpcGeneratedDir/init_py_web.dart" ]; then
+    cp "$scriptDir/templates/init_py_web.dart" "$grpcGeneratedDir/init_py_web.dart"
+fi
+if [ ! -f "$grpcGeneratedDir/health.pb.dart" ]; then
+    cp "$scriptDir/templates/health.pb.dart" "$grpcGeneratedDir/health.pb.dart"
+fi
+if [ ! -f "$grpcGeneratedDir/health.pbenum.dart" ]; then
+    cp "$scriptDir/templates/health.pbenum.dart" "$grpcGeneratedDir/health.pbenum.dart"
+fi
+if [ ! -f "$grpcGeneratedDir/health.pbgrpc.dart" ]; then
+    cp "$scriptDir/templates/health.pbgrpc.dart" "$grpcGeneratedDir/health.pbgrpc.dart"
+fi
+if [ ! -f "$grpcGeneratedDir/health.pbjson.dart" ]; then
+    cp "$scriptDir/templates/health.pbjson.dart" "$grpcGeneratedDir/health.pbjson.dart"
+fi
+
+
+
+echo "// !Will be rewriten upon \`prepare sources\` or \`build\` actions by Flutter-Python starter kit" > $grpcGeneratedDir/py_file_info.dart
+echo "const versionFileName = 'server_py_version.txt';" >> $grpcGeneratedDir/py_file_info.dart
+echo "const exeFileName = '$exeName';" >> $grpcGeneratedDir/py_file_info.dart
+echo "const currentFileVersionFromAssets = '';" >> $grpcGeneratedDir/py_file_info.dart
+
+cd $workingDir
+
+# Generate Python code
+mkdir -p $pythonDir
+mkdir -p $pythonDir/grpc_generated
+cd $protoDir # changing dir to avoid created nested folders in --dart_out beacause of implicitly following grpc namespaces
+$PYTHON -m grpc_tools.protoc -I. --python_out=$pythonDir/grpc_generated --grpc_python_out=$pythonDir/grpc_generated $protoFile
+cd $workingDir
+cp $scriptDir/templates/__init__.py $pythonDir/grpc_generated
+
+# Pyhton boilderplate code for running self-hosted gRPC server
+serverpy=$(cat << EOF
+$(<$scriptDir/templates/server.py)
+EOF
+)
+
+if [ ! -f "$pythonDir/server.py" ]; then
+echo "${serverpy//\$\{serviceName\}/$serviceName}" > "$pythonDir/server.py"
+fi
+
+if ! grep -q "^grpcio" "$pythonDir/requirements.txt"; then
+  echo -e "\ngrpcio" >> "$pythonDir/requirements.txt"
+fi
+
+if ! grep -q "^grpcio-health-checking" "$pythonDir/requirements.txt"; then
+  echo -e "\ngrpcio-health-checking" >> "$pythonDir/requirements.txt"
+fi
+
+GREEN='\033[0;32m'
+NC='\033[0m'
+echo -e "\n${GREEN}Dart/Flutter and Python bindings have been generated for '$proto' definition${NC}"
\ No newline at end of file
diff --git a/starter-kit/templates/LICENSE b/starter-kit/templates/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..ba3c9d72735ee7be588d66eb2b748b2f546b3913
--- /dev/null
+++ b/starter-kit/templates/LICENSE
@@ -0,0 +1,22 @@
+MIT License
+
+Flutter Python Starter Kit
+Copyright (c) 2023 Maxim Saplin
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/starter-kit/templates/__init__.py b/starter-kit/templates/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..d65abb68f622e9ffc641267f175c3e82aed2fb42
--- /dev/null
+++ b/starter-kit/templates/__init__.py
@@ -0,0 +1,3 @@
+import os
+import sys
+sys.path.insert(0, os.path.abspath(os.path.dirname(__file__)))
\ No newline at end of file
diff --git a/starter-kit/templates/client.dart b/starter-kit/templates/client.dart
new file mode 100644
index 0000000000000000000000000000000000000000..c5679668a567e1c02df2c392be04e76a4e70474d
--- /dev/null
+++ b/starter-kit/templates/client.dart
@@ -0,0 +1,36 @@
+import 'package:grpc/grpc_connection_interface.dart';
+// Conditional imports to enable single gRPC client creation for native and Web platfrom
+import 'client_native.dart'
+    if (dart.library.io) 'client_native.dart'
+    if (dart.library.html) 'client_web.dart';
+
+Map<String, ClientChannelBase> _clientChannelMap = {};
+
+int? _port;
+
+int get defaultPort => _port ?? 50055;
+
+/// Override the default port where the client will attempt to connect
+set defaultPort(int? value) {
+  _port = value;
+}
+
+String _defaultHost = 'localhost';
+
+String get defaultHost => _defaultHost;
+
+/// Set the default host address
+set defaultHost(String value) {
+  _defaultHost = value;
+}
+
+/// Lazily creates client channel, for each host/port pair there's one channel created and stored internally.
+/// You can use this channel to instatiate specigic client, i.e. `MyCleint(getClientChannel())`
+/// Parameters:
+/// - `host`: The host address. Default value is [defaultHost].
+/// - `port`: The port number. If not provided, the [defaultPort] value will be used.
+ClientChannelBase getClientChannel({String? host, int? port}) {
+  _clientChannelMap['$host:$port'] ??=
+      getGrpcClientChannel(host ?? defaultHost, port ?? defaultPort, false);
+  return _clientChannelMap['$host:$port']!;
+}
diff --git a/starter-kit/templates/client_native.dart b/starter-kit/templates/client_native.dart
new file mode 100644
index 0000000000000000000000000000000000000000..1c89c0ccd5cf1990345670d35ba9418f5b912e48
--- /dev/null
+++ b/starter-kit/templates/client_native.dart
@@ -0,0 +1,13 @@
+import 'package:grpc/grpc.dart';
+import 'package:grpc/grpc_connection_interface.dart';
+
+ClientChannelBase getGrpcClientChannel(String host, int port, bool useHttps) {
+  return ClientChannel(
+    host,
+    port: port,
+    options: ChannelOptions(
+        credentials: useHttps
+            ? const ChannelCredentials.secure()
+            : const ChannelCredentials.insecure()),
+  );
+}
diff --git a/starter-kit/templates/client_web.dart b/starter-kit/templates/client_web.dart
new file mode 100644
index 0000000000000000000000000000000000000000..b8e37630385ef19f940b808f24dbd2f7f26b38d0
--- /dev/null
+++ b/starter-kit/templates/client_web.dart
@@ -0,0 +1,7 @@
+import 'package:grpc/grpc_connection_interface.dart';
+import 'package:grpc/grpc_web.dart';
+
+ClientChannelBase getGrpcClientChannel(String host, int port, bool useHttp) {
+  return GrpcWebClientChannel.xhr(
+      Uri.parse('http${useHttp ? 's' : ''}://$host:$port'));
+}
diff --git a/starter-kit/templates/health.pb.dart b/starter-kit/templates/health.pb.dart
new file mode 100644
index 0000000000000000000000000000000000000000..16ee88eb374f87b6a4a65085ac8b27b90ad818a0
--- /dev/null
+++ b/starter-kit/templates/health.pb.dart
@@ -0,0 +1,122 @@
+//
+//  Generated code. Do not modify.
+//  source: health.proto
+//
+// @dart = 2.12
+
+// ignore_for_file: annotate_overrides, camel_case_types, comment_references
+// ignore_for_file: constant_identifier_names, library_prefixes
+// ignore_for_file: non_constant_identifier_names, prefer_final_fields
+// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
+
+import 'dart:core' as $core;
+
+import 'package:protobuf/protobuf.dart' as $pb;
+
+import 'health.pbenum.dart';
+
+export 'health.pbenum.dart';
+
+class HealthCheckRequest extends $pb.GeneratedMessage {
+  factory HealthCheckRequest({
+    $core.String? service,
+  }) {
+    final $result = create();
+    if (service != null) {
+      $result.service = service;
+    }
+    return $result;
+  }
+  HealthCheckRequest._() : super();
+  factory HealthCheckRequest.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory HealthCheckRequest.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'HealthCheckRequest', package: const $pb.PackageName(_omitMessageNames ? '' : 'grpc.health.v1'), createEmptyInstance: create)
+    ..aOS(1, _omitFieldNames ? '' : 'service')
+    ..hasRequiredFields = false
+  ;
+
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+  'Will be removed in next major version')
+  HealthCheckRequest clone() => HealthCheckRequest()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  HealthCheckRequest copyWith(void Function(HealthCheckRequest) updates) => super.copyWith((message) => updates(message as HealthCheckRequest)) as HealthCheckRequest;
+
+  $pb.BuilderInfo get info_ => _i;
+
+  @$core.pragma('dart2js:noInline')
+  static HealthCheckRequest create() => HealthCheckRequest._();
+  HealthCheckRequest createEmptyInstance() => create();
+  static $pb.PbList<HealthCheckRequest> createRepeated() => $pb.PbList<HealthCheckRequest>();
+  @$core.pragma('dart2js:noInline')
+  static HealthCheckRequest getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<HealthCheckRequest>(create);
+  static HealthCheckRequest? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  $core.String get service => $_getSZ(0);
+  @$pb.TagNumber(1)
+  set service($core.String v) { $_setString(0, v); }
+  @$pb.TagNumber(1)
+  $core.bool hasService() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearService() => clearField(1);
+}
+
+class HealthCheckResponse extends $pb.GeneratedMessage {
+  factory HealthCheckResponse({
+    HealthCheckResponse_ServingStatus? status,
+  }) {
+    final $result = create();
+    if (status != null) {
+      $result.status = status;
+    }
+    return $result;
+  }
+  HealthCheckResponse._() : super();
+  factory HealthCheckResponse.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory HealthCheckResponse.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'HealthCheckResponse', package: const $pb.PackageName(_omitMessageNames ? '' : 'grpc.health.v1'), createEmptyInstance: create)
+    ..e<HealthCheckResponse_ServingStatus>(1, _omitFieldNames ? '' : 'status', $pb.PbFieldType.OE, defaultOrMaker: HealthCheckResponse_ServingStatus.UNKNOWN, valueOf: HealthCheckResponse_ServingStatus.valueOf, enumValues: HealthCheckResponse_ServingStatus.values)
+    ..hasRequiredFields = false
+  ;
+
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+  'Will be removed in next major version')
+  HealthCheckResponse clone() => HealthCheckResponse()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  HealthCheckResponse copyWith(void Function(HealthCheckResponse) updates) => super.copyWith((message) => updates(message as HealthCheckResponse)) as HealthCheckResponse;
+
+  $pb.BuilderInfo get info_ => _i;
+
+  @$core.pragma('dart2js:noInline')
+  static HealthCheckResponse create() => HealthCheckResponse._();
+  HealthCheckResponse createEmptyInstance() => create();
+  static $pb.PbList<HealthCheckResponse> createRepeated() => $pb.PbList<HealthCheckResponse>();
+  @$core.pragma('dart2js:noInline')
+  static HealthCheckResponse getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<HealthCheckResponse>(create);
+  static HealthCheckResponse? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  HealthCheckResponse_ServingStatus get status => $_getN(0);
+  @$pb.TagNumber(1)
+  set status(HealthCheckResponse_ServingStatus v) { setField(1, v); }
+  @$pb.TagNumber(1)
+  $core.bool hasStatus() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearStatus() => clearField(1);
+}
+
+
+const _omitFieldNames = $core.bool.fromEnvironment('protobuf.omit_field_names');
+const _omitMessageNames = $core.bool.fromEnvironment('protobuf.omit_message_names');
diff --git a/starter-kit/templates/health.pbenum.dart b/starter-kit/templates/health.pbenum.dart
new file mode 100644
index 0000000000000000000000000000000000000000..58fc4f8b48fbca0d055879a21497505a5b7e2533
--- /dev/null
+++ b/starter-kit/templates/health.pbenum.dart
@@ -0,0 +1,36 @@
+//
+//  Generated code. Do not modify.
+//  source: health.proto
+//
+// @dart = 2.12
+
+// ignore_for_file: annotate_overrides, camel_case_types, comment_references
+// ignore_for_file: constant_identifier_names, library_prefixes
+// ignore_for_file: non_constant_identifier_names, prefer_final_fields
+// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
+
+import 'dart:core' as $core;
+
+import 'package:protobuf/protobuf.dart' as $pb;
+
+class HealthCheckResponse_ServingStatus extends $pb.ProtobufEnum {
+  static const HealthCheckResponse_ServingStatus UNKNOWN = HealthCheckResponse_ServingStatus._(0, _omitEnumNames ? '' : 'UNKNOWN');
+  static const HealthCheckResponse_ServingStatus SERVING = HealthCheckResponse_ServingStatus._(1, _omitEnumNames ? '' : 'SERVING');
+  static const HealthCheckResponse_ServingStatus NOT_SERVING = HealthCheckResponse_ServingStatus._(2, _omitEnumNames ? '' : 'NOT_SERVING');
+  static const HealthCheckResponse_ServingStatus SERVICE_UNKNOWN = HealthCheckResponse_ServingStatus._(3, _omitEnumNames ? '' : 'SERVICE_UNKNOWN');
+
+  static const $core.List<HealthCheckResponse_ServingStatus> values = <HealthCheckResponse_ServingStatus> [
+    UNKNOWN,
+    SERVING,
+    NOT_SERVING,
+    SERVICE_UNKNOWN,
+  ];
+
+  static final $core.Map<$core.int, HealthCheckResponse_ServingStatus> _byValue = $pb.ProtobufEnum.initByValue(values);
+  static HealthCheckResponse_ServingStatus? valueOf($core.int value) => _byValue[value];
+
+  const HealthCheckResponse_ServingStatus._($core.int v, $core.String n) : super(v, n);
+}
+
+
+const _omitEnumNames = $core.bool.fromEnvironment('protobuf.omit_enum_names');
diff --git a/starter-kit/templates/health.pbgrpc.dart b/starter-kit/templates/health.pbgrpc.dart
new file mode 100644
index 0000000000000000000000000000000000000000..bb346137fee1e942d94ffddf7eee9d384b0eca9e
--- /dev/null
+++ b/starter-kit/templates/health.pbgrpc.dart
@@ -0,0 +1,79 @@
+//
+//  Generated code. Do not modify.
+//  source: health.proto
+//
+// @dart = 2.12
+
+// ignore_for_file: annotate_overrides, camel_case_types, comment_references
+// ignore_for_file: constant_identifier_names, library_prefixes
+// ignore_for_file: non_constant_identifier_names, prefer_final_fields
+// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
+
+import 'dart:async' as $async;
+import 'dart:core' as $core;
+
+import 'package:grpc/service_api.dart' as $grpc;
+import 'package:protobuf/protobuf.dart' as $pb;
+
+import 'health.pb.dart' as $0;
+
+export 'health.pb.dart';
+
+@$pb.GrpcServiceName('grpc.health.v1.Health')
+class HealthClient extends $grpc.Client {
+  static final _$check = $grpc.ClientMethod<$0.HealthCheckRequest, $0.HealthCheckResponse>(
+      '/grpc.health.v1.Health/Check',
+      ($0.HealthCheckRequest value) => value.writeToBuffer(),
+      ($core.List<$core.int> value) => $0.HealthCheckResponse.fromBuffer(value));
+  static final _$watch = $grpc.ClientMethod<$0.HealthCheckRequest, $0.HealthCheckResponse>(
+      '/grpc.health.v1.Health/Watch',
+      ($0.HealthCheckRequest value) => value.writeToBuffer(),
+      ($core.List<$core.int> value) => $0.HealthCheckResponse.fromBuffer(value));
+
+  HealthClient($grpc.ClientChannel channel,
+      {$grpc.CallOptions? options,
+      $core.Iterable<$grpc.ClientInterceptor>? interceptors})
+      : super(channel, options: options,
+        interceptors: interceptors);
+
+  $grpc.ResponseFuture<$0.HealthCheckResponse> check($0.HealthCheckRequest request, {$grpc.CallOptions? options}) {
+    return $createUnaryCall(_$check, request, options: options);
+  }
+
+  $grpc.ResponseStream<$0.HealthCheckResponse> watch($0.HealthCheckRequest request, {$grpc.CallOptions? options}) {
+    return $createStreamingCall(_$watch, $async.Stream.fromIterable([request]), options: options);
+  }
+}
+
+@$pb.GrpcServiceName('grpc.health.v1.Health')
+abstract class HealthServiceBase extends $grpc.Service {
+  $core.String get $name => 'grpc.health.v1.Health';
+
+  HealthServiceBase() {
+    $addMethod($grpc.ServiceMethod<$0.HealthCheckRequest, $0.HealthCheckResponse>(
+        'Check',
+        check_Pre,
+        false,
+        false,
+        ($core.List<$core.int> value) => $0.HealthCheckRequest.fromBuffer(value),
+        ($0.HealthCheckResponse value) => value.writeToBuffer()));
+    $addMethod($grpc.ServiceMethod<$0.HealthCheckRequest, $0.HealthCheckResponse>(
+        'Watch',
+        watch_Pre,
+        false,
+        true,
+        ($core.List<$core.int> value) => $0.HealthCheckRequest.fromBuffer(value),
+        ($0.HealthCheckResponse value) => value.writeToBuffer()));
+  }
+
+  $async.Future<$0.HealthCheckResponse> check_Pre($grpc.ServiceCall call, $async.Future<$0.HealthCheckRequest> request) async {
+    return check(call, await request);
+  }
+
+  $async.Stream<$0.HealthCheckResponse> watch_Pre($grpc.ServiceCall call, $async.Future<$0.HealthCheckRequest> request) async* {
+    yield* watch(call, await request);
+  }
+
+  $async.Future<$0.HealthCheckResponse> check($grpc.ServiceCall call, $0.HealthCheckRequest request);
+  $async.Stream<$0.HealthCheckResponse> watch($grpc.ServiceCall call, $0.HealthCheckRequest request);
+}
diff --git a/starter-kit/templates/health.pbjson.dart b/starter-kit/templates/health.pbjson.dart
new file mode 100644
index 0000000000000000000000000000000000000000..bf29fbb60cf7217785871b3c2328cbbdd856c73d
--- /dev/null
+++ b/starter-kit/templates/health.pbjson.dart
@@ -0,0 +1,54 @@
+//
+//  Generated code. Do not modify.
+//  source: health.proto
+//
+// @dart = 2.12
+
+// ignore_for_file: annotate_overrides, camel_case_types, comment_references
+// ignore_for_file: constant_identifier_names, library_prefixes
+// ignore_for_file: non_constant_identifier_names, prefer_final_fields
+// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
+
+import 'dart:convert' as $convert;
+import 'dart:core' as $core;
+import 'dart:typed_data' as $typed_data;
+
+@$core.Deprecated('Use healthCheckRequestDescriptor instead')
+const HealthCheckRequest$json = {
+  '1': 'HealthCheckRequest',
+  '2': [
+    {'1': 'service', '3': 1, '4': 1, '5': 9, '10': 'service'},
+  ],
+};
+
+/// Descriptor for `HealthCheckRequest`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List healthCheckRequestDescriptor = $convert.base64Decode(
+    'ChJIZWFsdGhDaGVja1JlcXVlc3QSGAoHc2VydmljZRgBIAEoCVIHc2VydmljZQ==');
+
+@$core.Deprecated('Use healthCheckResponseDescriptor instead')
+const HealthCheckResponse$json = {
+  '1': 'HealthCheckResponse',
+  '2': [
+    {'1': 'status', '3': 1, '4': 1, '5': 14, '6': '.grpc.health.v1.HealthCheckResponse.ServingStatus', '10': 'status'},
+  ],
+  '4': [HealthCheckResponse_ServingStatus$json],
+};
+
+@$core.Deprecated('Use healthCheckResponseDescriptor instead')
+const HealthCheckResponse_ServingStatus$json = {
+  '1': 'ServingStatus',
+  '2': [
+    {'1': 'UNKNOWN', '2': 0},
+    {'1': 'SERVING', '2': 1},
+    {'1': 'NOT_SERVING', '2': 2},
+    {'1': 'SERVICE_UNKNOWN', '2': 3},
+  ],
+};
+
+/// Descriptor for `HealthCheckResponse`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List healthCheckResponseDescriptor = $convert.base64Decode(
+    'ChNIZWFsdGhDaGVja1Jlc3BvbnNlEkkKBnN0YXR1cxgBIAEoDjIxLmdycGMuaGVhbHRoLnYxLk'
+    'hlYWx0aENoZWNrUmVzcG9uc2UuU2VydmluZ1N0YXR1c1IGc3RhdHVzIk8KDVNlcnZpbmdTdGF0'
+    'dXMSCwoHVU5LTk9XThAAEgsKB1NFUlZJTkcQARIPCgtOT1RfU0VSVklORxACEhMKD1NFUlZJQ0'
+    'VfVU5LTk9XThAD');
+
diff --git a/starter-kit/templates/init_py.dart b/starter-kit/templates/init_py.dart
new file mode 100644
index 0000000000000000000000000000000000000000..80264403e40943a464819574295123f25ec16bb7
--- /dev/null
+++ b/starter-kit/templates/init_py.dart
@@ -0,0 +1,75 @@
+import 'package:app/grpc_generated/health.pbgrpc.dart';
+import 'package:flutter/foundation.dart';
+
+import 'client.dart';
+import 'init_py_native.dart'
+    if (dart.library.io) 'init_py_native.dart'
+    if (dart.library.html) 'init_py_web.dart';
+
+bool localPyStartSkipped = false;
+
+/// Initialize Python part, start self-hosted server for desktop, await for local
+/// or remote gRPC server to respond. If no response is resived within 15 second
+/// exception is thrown.
+/// Set [doNotStartPy] to `true` if you would like to use remote server
+Future<void> initPy([bool doNoStartPy = false]) async {
+  _initParamsFromEnvVars(doNoStartPy);
+
+  // Launch self-hosted servr or do nothing
+  await (localPyStartSkipped ? Future(() => null) : initPyImpl());
+
+  await _waitForServer();
+}
+
+Future<void> _waitForServer() async {
+  var cleint = HealthClient(getClientChannel());
+  var request = HealthCheckRequest();
+  var started = false;
+
+  for (var i = 0; i < 30; i++) {
+    try {
+      var r = await cleint.check(request);
+
+      if (r.status == HealthCheckResponse_ServingStatus.SERVING) {
+        started = true;
+        break;
+      }
+    } catch (_) {
+      await Future.delayed(const Duration(milliseconds: 500));
+    }
+  }
+
+  if (!started) {
+    throw "Can't connect to gRPC server";
+  }
+}
+
+void _initParamsFromEnvVars(bool doNoStartPy) {
+  // Hack to get access to --dart-define values in the web https://stackoverflow.com/questions/65647090/access-dart-define-environment-variables-inside-index-html
+  if (kIsWeb) {
+    initPyImpl();
+  }
+
+  var flag = const String.fromEnvironment('useRemote', defaultValue: 'false') ==
+      'true';
+  if (doNoStartPy || flag) {
+    localPyStartSkipped = true;
+  }
+
+  var hostOverride = const String.fromEnvironment('host', defaultValue: '');
+  if (hostOverride.isNotEmpty) {
+    defaultHost = hostOverride;
+  }
+
+  var portOverride =
+      int.tryParse(const String.fromEnvironment('port', defaultValue: ''));
+  if (portOverride != null) {
+    defaultPort = portOverride;
+  }
+}
+
+/// Searches for any processes that match Python server and kills those.
+/// Does nothing in the Web environment.
+Future<void> shutdownPyIfAny() {
+  return shutdownPyIfAnyImpl();
+}
diff --git a/starter-kit/templates/init_py_native.dart b/starter-kit/templates/init_py_native.dart
new file mode 100644
index 0000000000000000000000000000000000000000..09d8c76dcbeb456a474018269a00aad180c654fe
--- /dev/null
+++ b/starter-kit/templates/init_py_native.dart
@@ -0,0 +1,119 @@
+import 'dart:io';
+import 'package:flutter/foundation.dart';
+import 'package:flutter/services.dart';
+import 'package:path/path.dart' as p;
+import 'package:path_provider/path_provider.dart';
+
+import 'py_file_info.dart';
+import 'client.dart';
+
+Future<void> initPyImpl({String host = "localhost", int? port}) async {
+  var dir = await getApplicationSupportDirectory();
+  var filePath = await _prepareExecutable(dir.path);
+
+  // Ask OS to provide a free port if port is null and host is localhost
+  if (port == null && host == "localhost") {
+    var serverSocket = await ServerSocket.bind(InternetAddress.loopbackIPv4, 0);
+    port = serverSocket.port;
+    serverSocket.close();
+    defaultPort = port;
+  }
+
+  await shutdownPyIfAnyImpl();
+
+  if (defaultTargetPlatform == TargetPlatform.macOS ||
+      defaultTargetPlatform == TargetPlatform.linux) {
+    await Process.run("chmod", ["u+x", filePath]);
+  }
+  var p = await Process.start(filePath, [port.toString()]);
+
+  int? exitCode;
+
+  p.exitCode.then((v) {
+    exitCode = v;
+  });
+
+  // Give a couple of seconds to make sure there're no exceptions upon lanuching Python server
+
+  await Future.delayed(const Duration(seconds: 1));
+  if (exitCode != null) {
+    throw 'Failure while launching server process. It stopped right after starting. Exit code: $exitCode';
+  }
+}
+
+Future<String> _prepareExecutable(String directory) async {
+  var file = File(p.join(directory, _getAssetName()));
+  var versionFile = File(p.join(directory, versionFileName));
+
+  if (!file.existsSync()) {
+    ByteData pyExe =
+        await PlatformAssetBundle().load('assets/${_getAssetName()}');
+    await _writeFile(file, pyExe, versionFile);
+  } else {
+    // Check version file and asset sizes, version in the file and the constant
+    // If they do not match or the version file does not exist, update the executable and version file
+    var versionMismatch = false;
+    ByteData pyExe =
+        await PlatformAssetBundle().load('assets/${_getAssetName()}');
+    var loadedBinarySize = pyExe.buffer.lengthInBytes;
+    var currentBinarySize = await file.length();
+    if (loadedBinarySize != currentBinarySize) {
+      versionMismatch = true;
+    }
+
+    if (!versionFile.existsSync()) {
+      versionMismatch = true;
+    } else {
+      var fileVersion = await versionFile.readAsString();
+
+      if (fileVersion != currentFileVersionFromAssets) {
+        versionMismatch = true;
+      }
+    }
+
+    if (versionMismatch) {
+      await _writeFile(file, pyExe, versionFile);
+    }
+  }
+
+  return file.path;
+}
+
+Future<void> _writeFile(File file, ByteData pyExe, File versionFile) async {
+  if (file.existsSync()) {
+    file.deleteSync();
+  }
+  await file.create(recursive: true);
+  await file.writeAsBytes(pyExe.buffer.asUint8List());
+  await versionFile.writeAsString(currentFileVersionFromAssets);
+}
+
+/// Searches for any processes that match Python server and kills those
+Future<void> shutdownPyIfAnyImpl() async {
+  var name = _getAssetName();
+
+  switch (defaultTargetPlatform) {
+    case TargetPlatform.linux:
+    case TargetPlatform.macOS:
+      await Process.run('pkill', [name]);
+      break;
+    case TargetPlatform.windows:
+      await Process.run('taskkill', ['/F', '/IM', name]);
+      break;
+    default:
+      break;
+  }
+}
+
+String _getAssetName() {
+  var name = '';
+
+  if (defaultTargetPlatform == TargetPlatform.windows) {
+    name += '${exeFileName}_win.exe';
+  } else if (defaultTargetPlatform == TargetPlatform.macOS) {
+    name += '${exeFileName}_osx';
+  } else if (defaultTargetPlatform == TargetPlatform.linux) {
+    name += '${exeFileName}_lnx';
+  }
+  return name;
+}
diff --git a/starter-kit/templates/init_py_web.dart b/starter-kit/templates/init_py_web.dart
new file mode 100644
index 0000000000000000000000000000000000000000..f156f4dd2b4d017fb7f0a15d0d60bec944ddb16b
--- /dev/null
+++ b/starter-kit/templates/init_py_web.dart
@@ -0,0 +1,10 @@
+// ignore_for_file: avoid_web_libraries_in_flutter
+
+import 'dart:html' as html;
+
+Future<void> initPyImpl() async {
+  // Fire special dart event pushing env variables to Dart on app start
+  html.document.dispatchEvent(html.CustomEvent("dart_loaded"));
+}
+
+Future<void> shutdownPyIfAnyImpl() async {}
diff --git a/starter-kit/templates/server.py b/starter-kit/templates/server.py
new file mode 100644
index 0000000000000000000000000000000000000000..8066fccb961ed1a025e5c8f7d68fac7b9ee0969e
--- /dev/null
+++ b/starter-kit/templates/server.py
@@ -0,0 +1,29 @@
+import sys
+from concurrent import futures 
+import grpc
+from grpc_health.v1 import health_pb2_grpc
+from grpc_health.v1 import health
+# TODO, import generated gRPC stubs
+from grpc_generated import service_pb2_grpc
+# TODO, import your service implementation
+from number_sorting import NumberSortingService  
+
+def serve():
+  DEFAULT_PORT = 50055
+  # Get the port number from the command line parameter    
+  port = int(sys.argv[1]) if len(sys.argv) > 1 else DEFAULT_PORT
+  HOST = f'localhost:{port}'
+
+  server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
+
+  # TODO, add your gRPC service to self-hosted server, e.g.
+  service_pb2_grpc.add_NumberSortingServiceServicer_to_server(NumberSortingService(), server)
+  health_pb2_grpc.add_HealthServicer_to_server(health.HealthServicer(), server)
+
+  server.add_insecure_port(HOST)
+  print(f"gRPC server started and listening on {HOST}")
+  server.start()
+  server.wait_for_termination()
+  
+if __name__ == '__main__':
+  serve()