├── app ├── .fvm │ ├── flutter_sdk │ └── fvm_config.json ├── ios │ ├── Flutter │ │ ├── Debug.xcconfig │ │ ├── Release.xcconfig │ │ └── AppFrameworkInfo.plist │ ├── Runner │ │ ├── Runner-Bridging-Header.h │ │ ├── Assets.xcassets │ │ │ ├── LaunchImage.imageset │ │ │ │ ├── LaunchImage.png │ │ │ │ ├── LaunchImage@2x.png │ │ │ │ ├── LaunchImage@3x.png │ │ │ │ ├── README.md │ │ │ │ └── Contents.json │ │ │ └── AppIcon.appiconset │ │ │ │ ├── Icon-App-20x20@1x.png │ │ │ │ ├── Icon-App-20x20@2x.png │ │ │ │ ├── Icon-App-20x20@3x.png │ │ │ │ ├── Icon-App-29x29@1x.png │ │ │ │ ├── Icon-App-29x29@2x.png │ │ │ │ ├── Icon-App-29x29@3x.png │ │ │ │ ├── Icon-App-40x40@1x.png │ │ │ │ ├── Icon-App-40x40@2x.png │ │ │ │ ├── Icon-App-40x40@3x.png │ │ │ │ ├── Icon-App-60x60@2x.png │ │ │ │ ├── Icon-App-60x60@3x.png │ │ │ │ ├── Icon-App-76x76@1x.png │ │ │ │ ├── Icon-App-76x76@2x.png │ │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ │ ├── Icon-App-83.5x83.5@2x.png │ │ │ │ └── Contents.json │ │ ├── AppDelegate.swift │ │ ├── Base.lproj │ │ │ ├── Main.storyboard │ │ │ └── LaunchScreen.storyboard │ │ └── Info.plist │ ├── Runner.xcodeproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ ├── WorkspaceSettings.xcsettings │ │ │ │ └── IDEWorkspaceChecks.plist │ │ ├── xcshareddata │ │ │ └── xcschemes │ │ │ │ └── Runner.xcscheme │ │ └── project.pbxproj │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── WorkspaceSettings.xcsettings │ │ │ └── IDEWorkspaceChecks.plist │ └── .gitignore ├── lib │ ├── src │ │ ├── generated │ │ │ ├── index.dart │ │ │ ├── contract.pbenum.dart │ │ │ ├── contract.pbjson.dart │ │ │ ├── contract.pbgrpc.dart │ │ │ └── contract.pb.dart │ │ ├── config.dart │ │ ├── interceptors │ │ │ ├── logging_interceptor.dart │ │ │ └── inject_firebase_token_interceptor.dart │ │ ├── constants.dart │ │ ├── base_map.dart │ │ ├── all_hospitals.dart │ │ ├── common_widgets.dart │ │ ├── firebase_controllers.dart │ │ ├── map_view.dart │ │ └── search_hospitals_page.dart │ └── main.dart ├── web │ ├── favicon.png │ ├── icons │ │ ├── Icon-192.png │ │ └── Icon-512.png │ ├── manifest.json │ └── index.html ├── android │ ├── gradle.properties │ ├── app │ │ ├── src │ │ │ ├── main │ │ │ │ ├── res │ │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── drawable │ │ │ │ │ │ └── launch_background.xml │ │ │ │ │ ├── drawable-v21 │ │ │ │ │ │ └── launch_background.xml │ │ │ │ │ ├── values │ │ │ │ │ │ └── styles.xml │ │ │ │ │ └── values-night │ │ │ │ │ │ └── styles.xml │ │ │ │ ├── kotlin │ │ │ │ │ └── com │ │ │ │ │ │ └── myhospitalurl │ │ │ │ │ │ └── help │ │ │ │ │ │ └── hospitals_riverpod │ │ │ │ │ │ └── MainActivity.kt │ │ │ │ └── AndroidManifest.xml │ │ │ ├── debug │ │ │ │ └── AndroidManifest.xml │ │ │ └── profile │ │ │ │ └── AndroidManifest.xml │ │ └── build.gradle │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ ├── .gitignore │ ├── settings.gradle │ └── build.gradle ├── .vscode │ ├── settings.json │ └── launch.json ├── .metadata ├── .timetracker ├── .gitignore ├── test │ └── widget_test.dart ├── pubspec.yaml ├── README.md └── pubspec.lock ├── server ├── lib │ ├── hospitals.dart │ └── src │ │ ├── protos │ │ ├── generated │ │ │ ├── contract.pbenum.dart │ │ │ ├── contract.pbjson.dart │ │ │ ├── contract.pbgrpc.dart │ │ │ └── contract.pb.dart │ │ └── contract.proto │ │ ├── domain.dart │ │ ├── extensions.dart │ │ ├── utils.dart │ │ ├── server_interceptors.dart │ │ ├── server.dart │ │ ├── hospitals_repository.dart │ │ └── id_token_verifier.dart ├── CHANGELOG.md ├── .dockerignore ├── .gitignore ├── test │ ├── hospitals_test.dart │ └── extensions_test.dart ├── cloudbuild.yaml ├── pubspec.yaml ├── .vscode │ └── launch.json ├── analysis_options.yaml ├── Dockerfile ├── bin │ ├── main.dart │ └── client.dart ├── envoy.yaml ├── README.md └── pubspec.lock ├── hospitals.code-workspace └── README.md /app/.fvm/flutter_sdk: -------------------------------------------------------------------------------- 1 | /home/bett/fvm/versions/2.2.0 -------------------------------------------------------------------------------- /app/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /app/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /server/lib/hospitals.dart: -------------------------------------------------------------------------------- 1 | int calculate() { 2 | return 6 * 7; 3 | } 4 | -------------------------------------------------------------------------------- /app/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /server/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.0.0 2 | 3 | - Initial version, created by Stagehand 4 | -------------------------------------------------------------------------------- /app/.fvm/fvm_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "flutterSdkVersion": "3.0.5", 3 | "flavors": {} 4 | } -------------------------------------------------------------------------------- /app/lib/src/generated/index.dart: -------------------------------------------------------------------------------- 1 | export 'contract.pb.dart'; 2 | export 'contract.pbgrpc.dart'; 3 | -------------------------------------------------------------------------------- /app/web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bettdouglas/hospitals/HEAD/app/web/favicon.png -------------------------------------------------------------------------------- /app/web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bettdouglas/hospitals/HEAD/app/web/icons/Icon-192.png -------------------------------------------------------------------------------- /app/web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bettdouglas/hospitals/HEAD/app/web/icons/Icon-512.png -------------------------------------------------------------------------------- /app/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /server/.dockerignore: -------------------------------------------------------------------------------- 1 | .dockerignore 2 | Dockerfile 3 | build/ 4 | .dart_tool/ 5 | .git/ 6 | .github/ 7 | .gitignore 8 | .packages 9 | test/ -------------------------------------------------------------------------------- /app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bettdouglas/hospitals/HEAD/app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bettdouglas/hospitals/HEAD/app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bettdouglas/hospitals/HEAD/app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bettdouglas/hospitals/HEAD/app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bettdouglas/hospitals/HEAD/app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bettdouglas/hospitals/HEAD/app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bettdouglas/hospitals/HEAD/app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bettdouglas/hospitals/HEAD/app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bettdouglas/hospitals/HEAD/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bettdouglas/hospitals/HEAD/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bettdouglas/hospitals/HEAD/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bettdouglas/hospitals/HEAD/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bettdouglas/hospitals/HEAD/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bettdouglas/hospitals/HEAD/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bettdouglas/hospitals/HEAD/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bettdouglas/hospitals/HEAD/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bettdouglas/hospitals/HEAD/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bettdouglas/hospitals/HEAD/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bettdouglas/hospitals/HEAD/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bettdouglas/hospitals/HEAD/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bettdouglas/hospitals/HEAD/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bettdouglas/hospitals/HEAD/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bettdouglas/hospitals/HEAD/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | # Files and directories created by pub 2 | .dart_tool/ 3 | .packages 4 | 5 | # Conventional directory for build outputs 6 | build/ 7 | 8 | # Directory created by dartdoc 9 | doc/api/ 10 | -------------------------------------------------------------------------------- /server/test/hospitals_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:hospitals/hospitals.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | void main() { 5 | test('calculate', () { 6 | expect(calculate(), 42); 7 | }); 8 | } 9 | -------------------------------------------------------------------------------- /app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /server/cloudbuild.yaml: -------------------------------------------------------------------------------- 1 | steps: 2 | - name: "gcr.io/cloud-builders/docker" 3 | args: 4 | - build 5 | - "--tag=gcr.io/$PROJECT_ID/hospitals_dart_grpc" 6 | - "--file=./Dockerfile" 7 | - . 8 | images: 9 | - "gcr.io/$PROJECT_ID/hospitals_dart_grpc" -------------------------------------------------------------------------------- /app/android/app/src/main/kotlin/com/myhospitalurl/help/hospitals_riverpod/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.myhospitalurl.help.hospitals_riverpod 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /app/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip 7 | -------------------------------------------------------------------------------- /app/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "dart.flutterSdkPath": "/home/bett/fvm/versions/3.0.5", 3 | // Remove .fvm files from search 4 | "search.exclude": { 5 | "**/.fvm": true 6 | }, 7 | // Remove from file watching 8 | "files.watcherExclude": { 9 | "**/.fvm": true 10 | } 11 | } -------------------------------------------------------------------------------- /app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | -------------------------------------------------------------------------------- /app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: c5a4b4029c0798f37c4a39b479d7cb75daa7b05c 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /server/test/extensions_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:hospitals/src/extensions.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | void main() { 5 | group('String extensions test', () { 6 | test('capitalizeFirstLetter ', () { 7 | final c = 'uncapitalized first'; 8 | expect(c.capitalizeFirstLetter, 'Uncapitalized first'); 9 | }); 10 | }); 11 | } 12 | -------------------------------------------------------------------------------- /app/lib/src/generated/contract.pbenum.dart: -------------------------------------------------------------------------------- 1 | /// 2 | // Generated code. Do not modify. 3 | // source: lib/src/protos/contract.proto 4 | // 5 | // @dart = 2.12 6 | // ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields 7 | 8 | -------------------------------------------------------------------------------- /server/lib/src/protos/generated/contract.pbenum.dart: -------------------------------------------------------------------------------- 1 | /// 2 | // Generated code. Do not modify. 3 | // source: lib/src/protos/contract.proto 4 | // 5 | // @dart = 2.12 6 | // ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields 7 | 8 | -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /app/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /server/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: hospitals 2 | description: A sample command-line application. 3 | 4 | environment: 5 | sdk: '>=2.12.0 <3.0.0' 6 | 7 | dependencies: 8 | grpc: ^3.0.0 9 | protobuf: ^2.0.0 10 | csv: ^5.0.0 11 | dartz: 12 | dart_jts: 13 | basic_utils: ^5.2.0 14 | dart_jsonwebtoken: ^2.4.2 15 | logging: ^1.0.2 16 | 17 | dev_dependencies: 18 | pedantic: ^1.9.0 19 | test: ^1.14.4 20 | riverpod: ^0.14.0 -------------------------------------------------------------------------------- /server/lib/src/domain.dart: -------------------------------------------------------------------------------- 1 | class Hospital { 2 | final String name; 3 | final String type; 4 | final String placeName; 5 | final LatLng location; 6 | Hospital({ 7 | required this.name, 8 | required this.type, 9 | required this.placeName, 10 | required this.location, 11 | }); 12 | } 13 | 14 | class LatLng { 15 | final double lat; 16 | final double lon; 17 | LatLng({ 18 | required this.lat, 19 | required this.lon, 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /app/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /hospitals.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | } 6 | ], 7 | "launch": { 8 | "version": "0.2.0", 9 | "configurations": [ 10 | { 11 | "name": "Dart & Flutter", 12 | "request": "launch", 13 | "type": "dart" 14 | }, 15 | { 16 | "name": "Dart", 17 | "type": "dart", 18 | "request": "launch", 19 | "program": "server/bin/main.dart", 20 | "args": ["3001"], 21 | "env" : { 22 | "PORT": 8090 23 | } 24 | } 25 | ] 26 | } 27 | } -------------------------------------------------------------------------------- /app/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /server/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "hospitals", 9 | "request": "launch", 10 | "type": "dart", 11 | "program": "bin/main.dart", 12 | "env": {"PORT" : "3001"}, 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /server/lib/src/extensions.dart: -------------------------------------------------------------------------------- 1 | extension CapExtension on String { 2 | String get capitalizeFirstLetter => '${this[0].toUpperCase()}${substring(1)}'; 3 | String get allInCaps => toUpperCase(); 4 | String get capitalizeFirstofEach { 5 | final splitBySpace = split(' '); 6 | // return split(' ').map((str) => str.trim().inCaps).join(' '); 7 | return splitBySpace 8 | .map((e) => e.isEmpty ? '' : e.toLowerCase().capitalizeFirstLetter) 9 | .join(' '); 10 | } 11 | } 12 | 13 | class GetExtensions {} 14 | -------------------------------------------------------------------------------- /server/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # Defines a default set of lint rules enforced for 2 | # projects at Google. For details and rationale, 3 | # see https://github.com/dart-lang/pedantic#enabled-lints. 4 | include: package:pedantic/analysis_options.yaml 5 | 6 | # For lint rules and documentation, see http://dart-lang.github.io/linter/lints. 7 | # Uncomment to specify additional rules. 8 | linter: 9 | rules: 10 | annotate_overrides: false 11 | unnecessary_const: false 12 | 13 | # analyzer: 14 | # exclude: 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /app/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /app/android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/lib/src/config.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 2 | 3 | class Config { 4 | final String host; 5 | final int port; 6 | Config({ 7 | required this.host, 8 | required this.port, 9 | }); 10 | 11 | Config copyWith({ 12 | String? host, 13 | int? port, 14 | }) { 15 | return Config( 16 | host: host ?? this.host, 17 | port: port ?? this.port, 18 | ); 19 | } 20 | } 21 | 22 | final configFutureProvider = FutureProvider( 23 | (ref) async { 24 | await Future.delayed(Duration(seconds: 1)); 25 | return Config(host: 'localhost', port: 8080); 26 | }, 27 | ); 28 | -------------------------------------------------------------------------------- /app/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "hospitals_riverpod", 9 | "request": "launch", 10 | "type": "dart" 11 | }, 12 | { 13 | "name": "hospitals_riverpod (profile mode)", 14 | "request": "launch", 15 | "type": "dart", 16 | "flutterMode": "profile" 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /app/ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /app/web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hospitals_riverpod", 3 | "short_name": "hospitals_riverpod", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "A new Flutter project.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /app/.timetracker: -------------------------------------------------------------------------------- 1 | {"total":4415,"sessions":[{"begin":"2022-09-03T08:49:17+03:00","end":"2022-09-03T09:13:47+03:00","duration":1469},{"begin":"2022-09-03T09:18:32+03:00","end":"2022-09-03T09:51:40+03:00","duration":1987},{"begin":"2022-09-03T09:52:13+03:00","end":"2022-09-03T09:56:27+03:00","duration":254},{"begin":"2022-09-03T10:17:37+03:00","end":"2022-09-03T10:19:38+03:00","duration":121},{"begin":"2022-09-03T10:23:30+03:00","end":"2022-09-03T10:25:33+03:00","duration":123},{"begin":"2022-09-03T10:32:21+03:00","end":"2022-09-03T10:34:22+03:00","duration":121},{"begin":"2022-09-03T10:34:37+03:00","end":"2022-09-03T10:36:38+03:00","duration":121},{"begin":"2022-09-03T11:32:07+03:00","end":"2022-09-03T11:35:46+03:00","duration":219}]} -------------------------------------------------------------------------------- /app/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.5.0' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:4.1.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | jcenter() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /server/lib/src/utils.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:csv/csv.dart'; 3 | import 'package:csv/csv_settings_autodetection.dart'; 4 | 5 | class Utils { 6 | static Future> readHospitalsFromCSV() async { 7 | var healthCSV = File('assets/health.csv'); 8 | var csvString = await healthCSV.readAsString(); 9 | var d = FirstOccurrenceSettingsDetector(eols: ['\r\n', '\n']); 10 | 11 | var csvRows = 12 | CsvToListConverter().convert(csvString, csvSettingsDetector: d); 13 | return csvRows 14 | .map((e) => 15 | HospitalData(e[2], e[5], e[1].toDouble(), e[0].toDouble(), e[3])) 16 | .toList(); 17 | } 18 | } 19 | 20 | class HospitalData { 21 | final String name, type, location; 22 | final double latitude, longitude; 23 | 24 | HospitalData( 25 | this.name, this.type, this.latitude, this.longitude, this.location); 26 | } 27 | -------------------------------------------------------------------------------- /app/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /server/Dockerfile: -------------------------------------------------------------------------------- 1 | # Specify the Dart SDK base image version using dart: (ex: dart:2.12) 2 | FROM dart:stable AS build 3 | 4 | # Resolve app dependencies. 5 | WORKDIR /app 6 | COPY pubspec.* ./ 7 | RUN dart pub get 8 | 9 | # Copy app source code and AOT compile it. 10 | COPY . . 11 | # # Ensure packages are still up-to-date if anything has changed 12 | RUN dart pub get --offline 13 | RUN dart compile exe bin/main.dart -o bin/server 14 | 15 | # Build minimal serving image from AOT-compiled `/server` and required system 16 | # libraries and configuration files stored in `/runtime/` from the build stage. 17 | FROM scratch 18 | COPY --from=build /runtime/ / 19 | COPY assets/ assets/ 20 | COPY --from=build /app/bin/server /app/bin/ 21 | 22 | # Start server. 23 | CMD ["/app/bin/server"] 24 | 25 | 26 | # gcloud run deploy --image gcr.io/$PROJECT_ID/hospitals_dart_grpc --platform managed 27 | # gcloud builds submit --tag gcr.io/$PROJECT_ID/hospitals_dart_grpc --project $PROJECT_ID -------------------------------------------------------------------------------- /app/lib/src/interceptors/logging_interceptor.dart: -------------------------------------------------------------------------------- 1 | import 'package:grpc/grpc.dart'; 2 | import 'package:logging/logging.dart'; 3 | 4 | class RequestLoggingInterceptor extends ClientInterceptor { 5 | late Logger logger; 6 | 7 | RequestLoggingInterceptor() { 8 | logger = Logger('RequestLoggingInterceptor'); 9 | } 10 | 11 | @override 12 | ResponseStream interceptStreaming( 13 | ClientMethod method, 14 | Stream requests, 15 | CallOptions options, 16 | ClientStreamingInvoker invoker, 17 | ) { 18 | logger.info(method.path); 19 | return super.interceptStreaming(method, requests, options, invoker); 20 | } 21 | 22 | @override 23 | ResponseFuture interceptUnary( 24 | ClientMethod method, 25 | Q request, 26 | CallOptions options, 27 | ClientUnaryInvoker invoker, 28 | ) { 29 | logger.info(method.path); 30 | logger.info(options.metadata); 31 | return super.interceptUnary(method, request, options, invoker); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | .fvm/flutter_sdk 34 | 35 | # Web related 36 | lib/generated_plugin_registrant.dart 37 | 38 | # Symbolication related 39 | app.*.symbols 40 | 41 | # Obfuscation related 42 | app.*.map.json 43 | 44 | # Android Studio will place build artifacts here 45 | /android/app/debug 46 | /android/app/profile 47 | /android/app/release 48 | 49 | **/google-services.json 50 | **/firebase_app_id_file.json 51 | **/GoogleService-Info.plist 52 | **/firebase_options.dart 53 | 54 | -------------------------------------------------------------------------------- /app/android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gRPC in Dart & Flutter 2 | 3 | The purpose of this repo is to hold the code of the medium article [Building an end-to-end system in dart using grpc* & flutter](https://bettdougie.medium.com/building-an-end-to-end-system-using-grpc-flutter-part-1-d23b2356ed28). 4 | 5 | 6 | Please read the guide to see how to create a simple gRPC server using dartlang only. 7 | 8 | 1. [First Part](https://bettdougie.medium.com/building-an-end-to-end-system-using-grpc-flutter-part-1-d23b2356ed28) 9 | The first part focused on creating the backend in dart & an intro to gRPC in general. 10 | 2. [Second Part](https://bettdougie.medium.com/building-an-end-to-end-system-in-dart-using-grpc-flutter-part-2-with-riverpod-d08be216ebf5) 11 | The second part focused on creating the Flutter Client with state management using riverpod. 12 | 13 | - The [app](app/) [README](app/README.md) of the app in the second part focuses on how to use Client Interceptors in gRPC. 14 | - The [server](server/) [README](server/README.md) focuses on implementing Server Side gRPC interceptors which get called whenever any request is made to the server. That enables things like authentication, logging etc. 15 | 16 | -------------------------------------------------------------------------------- /app/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:hospitals_riverpod/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(MyApp()); 17 | 18 | // Verify that our counter starts at 0. 19 | expect(find.text('0'), findsOneWidget); 20 | expect(find.text('1'), findsNothing); 21 | 22 | // Tap the '+' icon and trigger a frame. 23 | await tester.tap(find.byIcon(Icons.add)); 24 | await tester.pump(); 25 | 26 | // Verify that our counter has incremented. 27 | expect(find.text('0'), findsNothing); 28 | expect(find.text('1'), findsOneWidget); 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /app/lib/src/constants.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 2 | import 'package:grpc/grpc.dart'; 3 | import 'package:hospitals_riverpod/src/firebase_controllers.dart'; 4 | 5 | import 'package:hospitals_riverpod/src/generated/index.dart'; 6 | import 'package:hospitals_riverpod/src/interceptors/inject_firebase_token_interceptor.dart'; 7 | import 'package:hospitals_riverpod/src/interceptors/logging_interceptor.dart'; 8 | 9 | final _channelOptions = ChannelOptions( 10 | credentials: ChannelCredentials.insecure(), // transmit unencrypted data., 11 | ); 12 | 13 | final _channel = ClientChannel( 14 | '0.0.0.0', // connect to localhost. Where it's served. 15 | port: 3001, 16 | options: _channelOptions, // pass the channelOptions above. 17 | ); 18 | 19 | final hostpitalClientProvider = Provider((ref) { 20 | final firebaseAuth = ref.read(firebaseAuthProvider); 21 | return HospitalServerClient( 22 | _channel, 23 | interceptors: [ 24 | // this logs the requests 25 | RequestLoggingInterceptor(), 26 | // this injects the firebase token into the request call 27 | AuthMetadataInterceptor(firebaseAuth: firebaseAuth), 28 | ], 29 | ); 30 | }); 31 | -------------------------------------------------------------------------------- /server/bin/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:developer'; 2 | import 'dart:io'; 3 | 4 | import 'package:grpc/grpc.dart'; 5 | import 'package:hospitals/src/server_interceptors.dart'; 6 | import 'package:hospitals/src/server.dart'; 7 | import 'package:hospitals/src/utils.dart'; 8 | import 'package:logging/logging.dart'; 9 | 10 | void main(List arguments) async { 11 | Logger.root.level = Level.ALL; 12 | Logger.root.onRecord.listen((LogRecord rec) { 13 | log( 14 | '${rec.loggerName}: ${rec.level.name}: ${rec.time}: ${rec.message}', 15 | ); 16 | }); 17 | 18 | var port = Platform.environment['PORT']; 19 | if (port == null) { 20 | throw Exception('Port variable is not defined'); 21 | } 22 | 23 | final intPort = int.parse(port); 24 | 25 | final hospitalsData = await Utils.readHospitalsFromCSV(); 26 | 27 | final interceptors = [ 28 | loggingInterceptor, 29 | authInterceptor, 30 | ]; 31 | 32 | final server = Server( 33 | [HospitalServer(hospitalData: hospitalsData)], 34 | interceptors, 35 | ); 36 | 37 | final ip = InternetAddress.anyIPv4; 38 | 39 | await server.serve(port: intPort, address: ip); 40 | 41 | log('Server running at port ${server.port}'); 42 | } 43 | -------------------------------------------------------------------------------- /server/lib/src/protos/contract.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | service HospitalServer { 4 | rpc GetHospitals (Empty) returns (Hospitals); 5 | rpc SearchHospitals (SearchQuery) returns (Hospitals); 6 | rpc NearestHospitals (NearestHospitalsRequest) returns (NearestHospitalsResponse); 7 | } 8 | 9 | message Empty {} 10 | 11 | message Hospital { 12 | string name = 1; 13 | Location coordinate = 2; 14 | string type = 3; 15 | string location = 4; 16 | } 17 | 18 | message Hospitals { 19 | repeated Hospital hospitals = 1; 20 | } 21 | 22 | message SearchQuery { 23 | string value = 1; 24 | } 25 | 26 | message Location { 27 | double latitude = 1; 28 | double longitude = 2; 29 | } 30 | 31 | message NearestHospitalsRequest { 32 | Location location = 1; 33 | int32 page = 2; 34 | int32 limit = 3; 35 | } 36 | 37 | message NearestHospitalsResponse { 38 | repeated Hospital hospitals = 1; 39 | } 40 | 41 | // // object name and variables inside the message 42 | // message Message { 43 | // // type variablename = id; 44 | // string name = 1; //String name 45 | // // collections are defined using repeated keyword 46 | // repeated string names = 2; // List names 47 | // int32 messageid = 3; // int messageid 48 | // } -------------------------------------------------------------------------------- /server/lib/src/server_interceptors.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:dartz/dartz.dart'; 4 | import 'package:grpc/grpc.dart'; 5 | import 'package:hospitals/src/id_token_verifier.dart'; 6 | import 'package:logging/logging.dart'; 7 | 8 | Future authInterceptor( 9 | ServiceCall call, 10 | ServiceMethod method, 11 | ) async { 12 | final metadata = call.clientMetadata ?? {}; 13 | final idToken = metadata['token']; 14 | if (idToken == null) { 15 | return GrpcError.unauthenticated('Missing Auth Token'); 16 | } 17 | final response = await verifyToken(idToken); 18 | return response.fold( 19 | (l) => GrpcError.unauthenticated(l), 20 | (claims) { 21 | metadata['user_id'] = claims['user_id']; 22 | return; 23 | }, 24 | ); 25 | } 26 | 27 | Future>> verifyToken( 28 | String idToken, 29 | ) async { 30 | // ... 31 | // returns claims if token 32 | return tokenVerifier.verifyToken(idToken); 33 | } 34 | 35 | Future loggingInterceptor( 36 | ServiceCall call, 37 | ServiceMethod method, 38 | ) async { 39 | final dateTime = DateTime.now(); 40 | final clientMetadata = call.clientMetadata ?? {}; 41 | final authority = clientMetadata[':authority']; 42 | final methodName = clientMetadata[':path']; 43 | final method = clientMetadata[':method']; 44 | final userAgent = clientMetadata['user-agent']; 45 | 46 | logger.info('$authority - - [$dateTime] $method $methodName $userAgent'); 47 | return null; 48 | } 49 | 50 | final logger = Logger('LoggingInterceptor'); 51 | final tokenVerifier = FirebaseTokenVerifier(); 52 | -------------------------------------------------------------------------------- /app/lib/src/interceptors/inject_firebase_token_interceptor.dart: -------------------------------------------------------------------------------- 1 | import 'package:firebase_auth/firebase_auth.dart'; 2 | import 'package:grpc/grpc.dart'; 3 | import 'package:logging/logging.dart'; 4 | 5 | class AuthMetadataInterceptor extends ClientInterceptor { 6 | final FirebaseAuth firebaseAuth; 7 | 8 | late Logger logger; 9 | 10 | AuthMetadataInterceptor({ 11 | required this.firebaseAuth, 12 | }) { 13 | logger = Logger('AuthMetadataInterceptor'); 14 | } 15 | 16 | Future _injectToken(Map metadata, String uri) async { 17 | final user = firebaseAuth.currentUser; 18 | if (user != null) { 19 | final token = await user.getIdToken(); 20 | metadata['token'] = token; 21 | } 22 | } 23 | 24 | @override 25 | ResponseStream interceptStreaming( 26 | ClientMethod method, 27 | Stream requests, 28 | CallOptions options, 29 | ClientStreamingInvoker invoker, 30 | ) { 31 | final modifiedOptions = options.mergedWith( 32 | CallOptions( 33 | providers: [ 34 | _injectToken, 35 | ], 36 | ), 37 | ); 38 | return super.interceptStreaming(method, requests, modifiedOptions, invoker); 39 | } 40 | 41 | @override 42 | ResponseFuture interceptUnary( 43 | ClientMethod method, 44 | Q request, 45 | CallOptions options, 46 | ClientUnaryInvoker invoker, 47 | ) { 48 | final modifiedOptions = options.mergedWith( 49 | CallOptions( 50 | providers: [ 51 | _injectToken, 52 | ], 53 | ), 54 | ); 55 | return super.interceptUnary(method, request, modifiedOptions, invoker); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | hospitals_riverpod 30 | 31 | 32 | 33 | 36 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /app/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /app/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | hospitals_riverpod 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /app/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion 31 30 | 31 | sourceSets { 32 | main.java.srcDirs += 'src/main/kotlin' 33 | } 34 | 35 | defaultConfig { 36 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 37 | applicationId "com.myhospitalurl.help.hospitals_riverpod" 38 | minSdkVersion 16 39 | targetSdkVersion 30 40 | versionCode flutterVersionCode.toInteger() 41 | versionName flutterVersionName 42 | } 43 | 44 | buildTypes { 45 | release { 46 | // TODO: Add your own signing config for the release build. 47 | // Signing with the debug keys for now, so `flutter run --release` works. 48 | signingConfig signingConfigs.debug 49 | } 50 | } 51 | } 52 | 53 | flutter { 54 | source '../..' 55 | } 56 | 57 | dependencies { 58 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 59 | } 60 | -------------------------------------------------------------------------------- /app/lib/src/base_map.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_map/flutter_map.dart'; 3 | import 'package:flutter_map_marker_cluster/flutter_map_marker_cluster.dart'; 4 | import 'package:latlong2/latlong.dart'; 5 | 6 | enum BaseTile { OSM, MB_DARK, MB_LIGHT, STAMEN } 7 | 8 | class BaseMap extends StatelessWidget { 9 | final LatLng center; 10 | final Iterable? markerLayerOptionsList; 11 | final Iterable? polygonLayerOptionsList; 12 | final Iterable? polylineLayerOptionsList; 13 | final MarkerClusterLayerOptions? markerClusterLayerOptions; 14 | final double zoom; 15 | 16 | const BaseMap({ 17 | Key? key, 18 | required this.center, 19 | this.markerLayerOptionsList, 20 | this.polygonLayerOptionsList, 21 | this.polylineLayerOptionsList, 22 | this.markerClusterLayerOptions, 23 | this.zoom = 5.0, 24 | }) : super(key: key); 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | final layers = [ 29 | TileLayerOptions( 30 | urlTemplate: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", 31 | subdomains: ['a', 'b', 'c'], 32 | ), 33 | ]; 34 | 35 | if (markerLayerOptionsList != null) { 36 | layers.addAll(markerLayerOptionsList!); 37 | } 38 | 39 | if (polygonLayerOptionsList != null) { 40 | layers.addAll(polygonLayerOptionsList!); 41 | } 42 | 43 | if (polylineLayerOptionsList != null) { 44 | layers.addAll(polylineLayerOptionsList!); 45 | } 46 | 47 | if (markerClusterLayerOptions != null) { 48 | layers.add(markerClusterLayerOptions!); 49 | } 50 | 51 | return Container( 52 | child: FlutterMap( 53 | options: MapOptions( 54 | center: this.center, 55 | zoom: zoom, 56 | onTap: (point) { 57 | print(point); 58 | }, 59 | plugins: [ 60 | MarkerClusterPlugin(), 61 | ], 62 | ), 63 | layers: layers, 64 | ), 65 | ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /app/lib/src/all_hospitals.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 3 | import 'package:hospitals_riverpod/src/common_widgets.dart'; 4 | import 'package:hospitals_riverpod/src/constants.dart'; 5 | import 'package:hospitals_riverpod/src/generated/contract.pb.dart'; 6 | import 'package:hospitals_riverpod/src/generated/contract.pbgrpc.dart'; 7 | 8 | final allHospitalsFutureProvider = FutureProvider>( 9 | (ref) async { 10 | final response = await ref 11 | .read(hostpitalClientProvider) 12 | .getHospitals(Empty()); // our future 13 | return response.hospitals; //returns a list of all the hospitals 14 | }, 15 | ); 16 | 17 | class AllHospitalsView extends ConsumerWidget { 18 | @override 19 | Widget build(BuildContext context, WidgetRef ref) { 20 | AsyncValue> state = ref.watch(allHospitalsFutureProvider); 21 | 22 | return state.when( 23 | // when we get the data 24 | data: (hospitals) => HospitalsListView(hospitals: hospitals), 25 | // when we're loading 26 | loading: () => Center(child: CircularProgressIndicator()), 27 | // when we have an error 28 | error: (err, stackTrace) => Padding( 29 | padding: const EdgeInsets.all(8.0), 30 | child: Column( 31 | mainAxisAlignment: MainAxisAlignment.center, 32 | children: [ 33 | Text(err.toString()), 34 | SizedBox(height: 20), 35 | ElevatedButton( 36 | onPressed: () => ref.refresh(allHospitalsFutureProvider), 37 | child: Container(height: 20, child: Text('Try Again')), 38 | ) 39 | ], 40 | ), 41 | ), 42 | ); 43 | } 44 | } 45 | 46 | class AllHospitalsPage extends StatelessWidget { 47 | const AllHospitalsPage({Key? key}) : super(key: key); 48 | 49 | @override 50 | Widget build(BuildContext context) { 51 | return Scaffold( 52 | appBar: AppBar( 53 | title: Text('All Kenyan Hospitals'), 54 | ), 55 | body: AllHospitalsView(), 56 | ); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /server/envoy.yaml: -------------------------------------------------------------------------------- 1 | admin: 2 | access_log_path: /tmp/admin_access.log 3 | address: 4 | socket_address: { address: 0.0.0.0, port_value: 9901 } 5 | 6 | static_resources: 7 | listeners: 8 | - name: listener_0 9 | address: 10 | socket_address: { address: 0.0.0.0, port_value: 8080 } 11 | filter_chains: 12 | - filters: 13 | - name: envoy.filters.network.http_connection_manager 14 | typed_config: 15 | "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager 16 | codec_type: auto 17 | stat_prefix: ingress_http 18 | route_config: 19 | name: local_route 20 | virtual_hosts: 21 | - name: local_service 22 | domains: ["*"] 23 | routes: 24 | - match: { prefix: "/" } 25 | route: 26 | cluster: greeter_service 27 | max_stream_duration: 28 | grpc_timeout_header_max: 0s 29 | cors: 30 | allow_origin_string_match: 31 | - prefix: "*" 32 | allow_methods: GET, PUT, DELETE, POST, OPTIONS 33 | allow_headers: keep-alive,user-agent,cache-control,content-type,content-transfer-encoding,x-accept-content-transfer-encoding,x-accept-response-streaming,x-user-agent,x-grpc-web,grpc-timeout 34 | max_age: "1728000" 35 | expose_headers: id,token,grpc-status,grpc-message 36 | http_filters: 37 | - name: envoy.filters.http.grpc_web 38 | - name: envoy.filters.http.cors 39 | - name: envoy.filters.http.router 40 | clusters: 41 | - name: greeter_service 42 | connect_timeout: 0.25s 43 | type: logical_dns 44 | http2_protocol_options: {} 45 | lb_policy: round_robin 46 | # win/mac hosts: Use address: host.docker.internal instead of address: localhost in the line below 47 | load_assignment: 48 | cluster_name: cluster_0 49 | endpoints: 50 | - lb_endpoints: 51 | - endpoint: 52 | address: 53 | socket_address: 54 | address: 0.0.0.0 55 | port_value: 3001 -------------------------------------------------------------------------------- /app/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 13 | 17 | 21 | 26 | 30 | 31 | 32 | 33 | 34 | 35 | 37 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /server/bin/client.dart: -------------------------------------------------------------------------------- 1 | import 'package:grpc/grpc.dart'; 2 | import 'package:hospitals/src/protos/generated/contract.pbgrpc.dart'; 3 | // import 'package:riverpod/riverpod.dart'; 4 | 5 | // class TestInterceptor extends ClientInterceptor { 6 | // void run() {} 7 | 8 | // @override 9 | // ResponseFuture interceptUnary( 10 | // ClientMethod method, 11 | // Q request, 12 | // CallOptions options, 13 | // invoker, 14 | // ) { 15 | // print(request); 16 | // print(method.path); 17 | // return super.interceptUnary(method, request, options, invoker); 18 | // } 19 | // } 20 | 21 | void main(List args) async { 22 | final channelOptions = ChannelOptions( 23 | // credentials: ChannelCredentials.insecure(), // transmit unencrypted data., 24 | credentials: ChannelCredentials.secure( 25 | 26 | ), 27 | ); 28 | 29 | final channel = ClientChannel( 30 | '0.0.0.0', 31 | port: 8080, 32 | // 'hospitals-dart-grpc-fysuv2s5na-ez.a.run.app', // connect to localhost. Where it's served. 33 | options: channelOptions, // pass the channelOptions above. 34 | ); 35 | 36 | final client = HospitalServerClient( 37 | channel, 38 | interceptors: [], 39 | ); // this handles communication to the server 40 | final allHospitals = await client.getHospitals(Empty()); 41 | print(allHospitals.hospitals); 42 | await channel.terminate(); 43 | } 44 | // communicate with server like 45 | 46 | // final sa = SearchAhead(client); 47 | 48 | // sa.addListener((state) { 49 | // print(state); 50 | // }); 51 | 52 | // while (true) { 53 | // final typedLine = stdin.readLineSync(); 54 | // sa.add(typedLine); 55 | // } 56 | // } 57 | 58 | // class SearchAhead extends StateNotifier> { 59 | // final HospitalServerClient stub; 60 | 61 | // SearchAhead(this.stub) : super([]) { 62 | // _controller.stream.asyncMap(_searchHospital).listen((event) { 63 | // state = event.hospitals; 64 | // }); 65 | // } 66 | 67 | // final _controller = StreamController(); 68 | 69 | // Future _searchHospital(String query) async { 70 | // print('Searching for $query'); 71 | // final response = await stub.searchHospitals( 72 | // SearchQuery( 73 | // value: query, 74 | // ), 75 | // ); 76 | // return response; 77 | // } 78 | 79 | // void add(String? keyword) async { 80 | // print('added $keyword'); 81 | // _controller.add(keyword!); 82 | // } 83 | // } 84 | -------------------------------------------------------------------------------- /app/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /server/lib/src/server.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:grpc/grpc.dart'; 4 | import 'package:grpc/src/server/call.dart'; 5 | import 'package:hospitals/src/extensions.dart'; 6 | import 'package:hospitals/src/protos/generated/contract.pbgrpc.dart'; 7 | import 'package:hospitals/src/utils.dart'; 8 | 9 | class HospitalServer extends HospitalServerServiceBase { 10 | // start server after successfully reading list of hospitals 11 | final List hospitalData; 12 | late List _hospitals; 13 | 14 | HospitalServer({required this.hospitalData}) { 15 | _hospitals = hospitalData.map((hd) { 16 | final coordinate = Location( 17 | latitude: hd.latitude, 18 | longitude: hd.longitude, 19 | ); 20 | return Hospital( 21 | name: hd.name.capitalizeFirstofEach, 22 | location: hd.location, 23 | coordinate: coordinate, 24 | type: hd.type, 25 | ); 26 | }).toList(); 27 | } 28 | 29 | @override 30 | Future getHospitals(ServiceCall call, Empty request) async { 31 | return Hospitals(hospitals: _hospitals); 32 | } 33 | 34 | @override 35 | Future searchHospitals( 36 | ServiceCall call, 37 | SearchQuery request, 38 | ) async { 39 | final searchTerm = request.value; 40 | final filtered = _hospitals.where( 41 | (hospital) => hospital.name.toLowerCase().contains( 42 | searchTerm.toLowerCase(), 43 | ), 44 | ); 45 | return Hospitals( 46 | hospitals: filtered, 47 | ); 48 | } 49 | 50 | @override 51 | Stream streamNRandomHospitals( 52 | ServiceCall call, 53 | StreamNRandomHospitalsRequest request, 54 | ) async* { 55 | final count = request.count; 56 | print(count); 57 | final stream = Stream.periodic(Duration(seconds: 5)).map( 58 | (event) => StreamNRandomHospitalsResponse( 59 | hospitals: _hospitals.randomN(count), 60 | ), 61 | ); 62 | print(stream); 63 | yield* stream; 64 | } 65 | 66 | @override 67 | Future nearestHospitals( 68 | ServiceCall call, 69 | NearestHospitalsRequest request, 70 | ) { 71 | // TODO: implement nearestHospitals 72 | throw UnimplementedError(); 73 | } 74 | } 75 | 76 | extension RandomN on List { 77 | T get random { 78 | return elementAt(Random().nextInt(length)); 79 | } 80 | 81 | List randomN(int count) { 82 | final elements = {}; 83 | while (elements.length < count) { 84 | elements.add(random); 85 | } 86 | return elements.toList(); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /app/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: hospitals_riverpod 2 | description: How to connect to a grpc service running in dart/another process 3 | 4 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 5 | 6 | version: 1.0.0+1 7 | 8 | environment: 9 | sdk: ">=2.14.0 <3.0.0" 10 | 11 | dependencies: 12 | flutter: 13 | sdk: flutter 14 | flutter_riverpod: 1.0.4 15 | grpc: ^3.0.0 16 | protobuf: ^2.0.0 17 | flutter_map: ^0.13.1 18 | flutter_map_marker_cluster: ^0.4.0 19 | modal_bottom_sheet: ^2.0.0 20 | random_color_scheme: 21 | firebase_core: 22 | firebase_auth: 23 | logging: ^1.0.2 24 | 25 | dev_dependencies: 26 | flutter_test: 27 | sdk: flutter 28 | 29 | # For information on the generic Dart part of this file, see the 30 | # following page: https://dart.dev/tools/pub/pubspec 31 | 32 | # The following section is specific to Flutter. 33 | flutter: 34 | 35 | # The following line ensures that the Material Icons font is 36 | # included with your application, so that you can use the icons in 37 | # the material Icons class. 38 | uses-material-design: true 39 | 40 | # To add assets to your application, add an assets section, like this: 41 | # assets: 42 | # - images/a_dot_burr.jpeg 43 | # - images/a_dot_ham.jpeg 44 | 45 | # An image asset can refer to one or more resolution-specific "variants", see 46 | # https://flutter.dev/assets-and-images/#resolution-aware. 47 | 48 | # For details regarding adding assets from package dependencies, see 49 | # https://flutter.dev/assets-and-images/#from-packages 50 | 51 | # To add custom fonts to your application, add a fonts section here, 52 | # in this "flutter" section. Each entry in this list should have a 53 | # "family" key with the font family name, and a "fonts" key with a 54 | # list giving the asset and other descriptors for the font. For 55 | # example: 56 | # fonts: 57 | # - family: Schyler 58 | # fonts: 59 | # - asset: fonts/Schyler-Regular.ttf 60 | # - asset: fonts/Schyler-Italic.ttf 61 | # style: italic 62 | # - family: Trajan Pro 63 | # fonts: 64 | # - asset: fonts/TrajanPro.ttf 65 | # - asset: fonts/TrajanPro_Bold.ttf 66 | # weight: 700 67 | # 68 | # For details regarding fonts from package dependencies, 69 | # see https://flutter.dev/custom-fonts/#from-packages 70 | scripts: 71 | build: fvm flutter pub run build_runner build --delete-conflicting-outputs 72 | watch: fvm flutter pub run build_runner watch --delete-conflicting-outputs 73 | buildapp: fvm flutter build appbundle 74 | buildapks: fvm flutter build apk --split-per-abi 75 | test: fvm flutter test 76 | get: fvm flutter pub get -------------------------------------------------------------------------------- /server/lib/src/hospitals_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:dart_jts/dart_jts.dart'; 2 | import 'package:dartz/dartz.dart'; 3 | 4 | import 'package:hospitals/src/domain.dart'; 5 | 6 | class HospitalsRepository { 7 | final List hospitalList; 8 | HospitalsRepository({ 9 | required this.hospitalList, 10 | }) { 11 | final db = STRtree(); 12 | hospitalList.forEach((hospital) { 13 | final envelope = _gf 14 | .createPoint(Coordinate(hospital.location.lon, hospital.location.lat)) 15 | .envelope!; 16 | db.insert(envelope, hospital); 17 | }); 18 | _database = Some(db); 19 | } 20 | 21 | Option _database = None(); 22 | final _gf = GeometryFactory.defaultPrecision(); 23 | 24 | Future>> allHospitals() async { 25 | return _database.toEither(() => Exception('Database is not ready')).map( 26 | (r) { 27 | return r.itemsTree() as List; 28 | }, 29 | ); 30 | } 31 | 32 | Future>> searchHospitals(String name) async { 33 | return _database.toEither(() => Exception('Database is not ready')).map( 34 | (r) { 35 | return (r.itemsTree() as List) 36 | .where((e) => e.name.contains(name)) 37 | .toList(); 38 | }, 39 | ); 40 | } 41 | 42 | Future>> nearestHospitals( 43 | LatLng latLng, 44 | int page, 45 | int count, 46 | ) async { 47 | // return _database.toEither(() => Exception('Database is not ready')).map( 48 | // (r) { 49 | // final one = Boundable(); 50 | // return r.nearestNeighbourK(BoundablePair(boundable1, boundable2, itemDistance), k) 51 | // }, 52 | // ); 53 | throw UnimplementedError(); 54 | } 55 | } 56 | 57 | extension AsCoordinate on LatLng { 58 | Point asPoint(GeometryFactory gf) { 59 | return gf.createPoint(Coordinate(lon, lat)); 60 | } 61 | } 62 | 63 | // class MockHospitalsRepository implements HospitalsRepository { 64 | // @override 65 | // Future>> allHospitals() { 66 | // // TODO: implement allHospitals 67 | // throw UnimplementedError(); 68 | // } 69 | 70 | // @override 71 | // // TODO: implement hospitalList 72 | // List get hospitalList => throw UnimplementedError(); 73 | 74 | // @override 75 | // Future>> nearestHospitals() { 76 | // // TODO: implement nearestHospitals 77 | // throw UnimplementedError(); 78 | // } 79 | 80 | // @override 81 | // Future>> searchHospitals(String name) { 82 | // // TODO: implement searchHospitals 83 | // throw UnimplementedError(); 84 | // } 85 | // } 86 | 87 | // class TypedSTRTree {} 88 | -------------------------------------------------------------------------------- /app/lib/src/common_widgets.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:hospitals_riverpod/src/generated/index.dart'; 3 | 4 | class LoadingView extends StatelessWidget { 5 | const LoadingView({Key? key, required this.msg}) : super(key: key); 6 | 7 | final String msg; 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return Column( 12 | crossAxisAlignment: CrossAxisAlignment.center, 13 | mainAxisAlignment: MainAxisAlignment.center, 14 | children: [ 15 | Text(msg), 16 | SizedBox(height: 20), 17 | CircularProgressIndicator(), 18 | ], 19 | ); 20 | } 21 | } 22 | 23 | class ErrorView extends StatelessWidget { 24 | const ErrorView({ 25 | Key? key, 26 | required this.error, 27 | required this.msg, 28 | this.stackTrace, 29 | required this.retryFunction, 30 | }) : super(key: key); 31 | 32 | final Object error; 33 | final StackTrace? stackTrace; 34 | final String msg; 35 | final VoidCallback retryFunction; 36 | 37 | @override 38 | Widget build(BuildContext context) { 39 | return Column( 40 | crossAxisAlignment: CrossAxisAlignment.center, 41 | mainAxisAlignment: MainAxisAlignment.center, 42 | children: [ 43 | SizedBox(height: 20), 44 | Icon(Icons.error), 45 | SizedBox(height: 20), 46 | Text(error.toString()), 47 | SizedBox(height: 20), 48 | ElevatedButton( 49 | onPressed: retryFunction, 50 | child: Container( 51 | alignment: Alignment.center, 52 | height: 20, 53 | child: Text('Try Again?'), 54 | ), 55 | ) 56 | ], 57 | ); 58 | } 59 | } 60 | 61 | class HospitalsListView extends StatelessWidget { 62 | final List hospitals; 63 | 64 | const HospitalsListView({ 65 | Key? key, 66 | required this.hospitals, 67 | }) : super(key: key); 68 | 69 | @override 70 | Widget build(BuildContext context) { 71 | return ListView.builder( 72 | itemBuilder: (context, idx) => HospitalTile( 73 | hospital: hospitals[idx], 74 | ), 75 | itemCount: hospitals.length, 76 | ); 77 | } 78 | } 79 | 80 | class HospitalTile extends StatelessWidget { 81 | final Hospital hospital; 82 | 83 | const HospitalTile({ 84 | Key? key, 85 | required this.hospital, 86 | }) : super(key: key); 87 | 88 | @override 89 | Widget build(BuildContext context) { 90 | return ListTile( 91 | leading: CircleAvatar( 92 | child: Text(hospital.name[0]), 93 | backgroundColor: Theme.of(context).colorScheme.onBackground, 94 | ), 95 | title: Text(hospital.name), 96 | subtitle: Row( 97 | children: [ 98 | Text(hospital.location), 99 | Text( 100 | '\t(${hospital.coordinate.latitude},${hospital.coordinate.longitude})', 101 | ) 102 | ], 103 | ), 104 | ); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /app/lib/src/generated/contract.pbjson.dart: -------------------------------------------------------------------------------- 1 | /// 2 | // Generated code. Do not modify. 3 | // source: lib/src/protos/contract.proto 4 | // 5 | // @dart = 2.12 6 | // ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package 7 | 8 | import 'dart:core' as $core; 9 | import 'dart:convert' as $convert; 10 | import 'dart:typed_data' as $typed_data; 11 | @$core.Deprecated('Use emptyDescriptor instead') 12 | const Empty$json = const { 13 | '1': 'Empty', 14 | }; 15 | 16 | /// Descriptor for `Empty`. Decode as a `google.protobuf.DescriptorProto`. 17 | final $typed_data.Uint8List emptyDescriptor = $convert.base64Decode('CgVFbXB0eQ=='); 18 | @$core.Deprecated('Use hospitalDescriptor instead') 19 | const Hospital$json = const { 20 | '1': 'Hospital', 21 | '2': const [ 22 | const {'1': 'name', '3': 1, '4': 1, '5': 9, '10': 'name'}, 23 | const {'1': 'coordinate', '3': 2, '4': 1, '5': 11, '6': '.Location', '10': 'coordinate'}, 24 | const {'1': 'type', '3': 3, '4': 1, '5': 9, '10': 'type'}, 25 | const {'1': 'location', '3': 4, '4': 1, '5': 9, '10': 'location'}, 26 | ], 27 | }; 28 | 29 | /// Descriptor for `Hospital`. Decode as a `google.protobuf.DescriptorProto`. 30 | final $typed_data.Uint8List hospitalDescriptor = $convert.base64Decode('CghIb3NwaXRhbBISCgRuYW1lGAEgASgJUgRuYW1lEikKCmNvb3JkaW5hdGUYAiABKAsyCS5Mb2NhdGlvblIKY29vcmRpbmF0ZRISCgR0eXBlGAMgASgJUgR0eXBlEhoKCGxvY2F0aW9uGAQgASgJUghsb2NhdGlvbg=='); 31 | @$core.Deprecated('Use hospitalsDescriptor instead') 32 | const Hospitals$json = const { 33 | '1': 'Hospitals', 34 | '2': const [ 35 | const {'1': 'hospitals', '3': 1, '4': 3, '5': 11, '6': '.Hospital', '10': 'hospitals'}, 36 | ], 37 | }; 38 | 39 | /// Descriptor for `Hospitals`. Decode as a `google.protobuf.DescriptorProto`. 40 | final $typed_data.Uint8List hospitalsDescriptor = $convert.base64Decode('CglIb3NwaXRhbHMSJwoJaG9zcGl0YWxzGAEgAygLMgkuSG9zcGl0YWxSCWhvc3BpdGFscw=='); 41 | @$core.Deprecated('Use searchQueryDescriptor instead') 42 | const SearchQuery$json = const { 43 | '1': 'SearchQuery', 44 | '2': const [ 45 | const {'1': 'value', '3': 1, '4': 1, '5': 9, '10': 'value'}, 46 | ], 47 | }; 48 | 49 | /// Descriptor for `SearchQuery`. Decode as a `google.protobuf.DescriptorProto`. 50 | final $typed_data.Uint8List searchQueryDescriptor = $convert.base64Decode('CgtTZWFyY2hRdWVyeRIUCgV2YWx1ZRgBIAEoCVIFdmFsdWU='); 51 | @$core.Deprecated('Use locationDescriptor instead') 52 | const Location$json = const { 53 | '1': 'Location', 54 | '2': const [ 55 | const {'1': 'latitude', '3': 1, '4': 1, '5': 1, '10': 'latitude'}, 56 | const {'1': 'longitude', '3': 2, '4': 1, '5': 1, '10': 'longitude'}, 57 | ], 58 | }; 59 | 60 | /// Descriptor for `Location`. Decode as a `google.protobuf.DescriptorProto`. 61 | final $typed_data.Uint8List locationDescriptor = $convert.base64Decode('CghMb2NhdGlvbhIaCghsYXRpdHVkZRgBIAEoAVIIbGF0aXR1ZGUSHAoJbG9uZ2l0dWRlGAIgASgBUglsb25naXR1ZGU='); 62 | -------------------------------------------------------------------------------- /server/lib/src/protos/generated/contract.pbjson.dart: -------------------------------------------------------------------------------- 1 | /// 2 | // Generated code. Do not modify. 3 | // source: lib/src/protos/contract.proto 4 | // 5 | // @dart = 2.12 6 | // ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package 7 | 8 | import 'dart:core' as $core; 9 | import 'dart:convert' as $convert; 10 | import 'dart:typed_data' as $typed_data; 11 | @$core.Deprecated('Use emptyDescriptor instead') 12 | const Empty$json = const { 13 | '1': 'Empty', 14 | }; 15 | 16 | /// Descriptor for `Empty`. Decode as a `google.protobuf.DescriptorProto`. 17 | final $typed_data.Uint8List emptyDescriptor = $convert.base64Decode('CgVFbXB0eQ=='); 18 | @$core.Deprecated('Use hospitalDescriptor instead') 19 | const Hospital$json = const { 20 | '1': 'Hospital', 21 | '2': const [ 22 | const {'1': 'name', '3': 1, '4': 1, '5': 9, '10': 'name'}, 23 | const {'1': 'coordinate', '3': 2, '4': 1, '5': 11, '6': '.Location', '10': 'coordinate'}, 24 | const {'1': 'type', '3': 3, '4': 1, '5': 9, '10': 'type'}, 25 | const {'1': 'location', '3': 4, '4': 1, '5': 9, '10': 'location'}, 26 | ], 27 | }; 28 | 29 | /// Descriptor for `Hospital`. Decode as a `google.protobuf.DescriptorProto`. 30 | final $typed_data.Uint8List hospitalDescriptor = $convert.base64Decode('CghIb3NwaXRhbBISCgRuYW1lGAEgASgJUgRuYW1lEikKCmNvb3JkaW5hdGUYAiABKAsyCS5Mb2NhdGlvblIKY29vcmRpbmF0ZRISCgR0eXBlGAMgASgJUgR0eXBlEhoKCGxvY2F0aW9uGAQgASgJUghsb2NhdGlvbg=='); 31 | @$core.Deprecated('Use hospitalsDescriptor instead') 32 | const Hospitals$json = const { 33 | '1': 'Hospitals', 34 | '2': const [ 35 | const {'1': 'hospitals', '3': 1, '4': 3, '5': 11, '6': '.Hospital', '10': 'hospitals'}, 36 | ], 37 | }; 38 | 39 | /// Descriptor for `Hospitals`. Decode as a `google.protobuf.DescriptorProto`. 40 | final $typed_data.Uint8List hospitalsDescriptor = $convert.base64Decode('CglIb3NwaXRhbHMSJwoJaG9zcGl0YWxzGAEgAygLMgkuSG9zcGl0YWxSCWhvc3BpdGFscw=='); 41 | @$core.Deprecated('Use searchQueryDescriptor instead') 42 | const SearchQuery$json = const { 43 | '1': 'SearchQuery', 44 | '2': const [ 45 | const {'1': 'value', '3': 1, '4': 1, '5': 9, '10': 'value'}, 46 | ], 47 | }; 48 | 49 | /// Descriptor for `SearchQuery`. Decode as a `google.protobuf.DescriptorProto`. 50 | final $typed_data.Uint8List searchQueryDescriptor = $convert.base64Decode('CgtTZWFyY2hRdWVyeRIUCgV2YWx1ZRgBIAEoCVIFdmFsdWU='); 51 | @$core.Deprecated('Use locationDescriptor instead') 52 | const Location$json = const { 53 | '1': 'Location', 54 | '2': const [ 55 | const {'1': 'latitude', '3': 1, '4': 1, '5': 1, '10': 'latitude'}, 56 | const {'1': 'longitude', '3': 2, '4': 1, '5': 1, '10': 'longitude'}, 57 | ], 58 | }; 59 | 60 | /// Descriptor for `Location`. Decode as a `google.protobuf.DescriptorProto`. 61 | final $typed_data.Uint8List locationDescriptor = $convert.base64Decode('CghMb2NhdGlvbhIaCghsYXRpdHVkZRgBIAEoAVIIbGF0aXR1ZGUSHAoJbG9uZ2l0dWRlGAIgASgBUglsb25naXR1ZGU='); 62 | -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /app/lib/src/generated/contract.pbgrpc.dart: -------------------------------------------------------------------------------- 1 | /// 2 | // Generated code. Do not modify. 3 | // source: lib/src/protos/contract.proto 4 | // 5 | // @dart = 2.12 6 | // ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields 7 | 8 | import 'dart:async' as $async; 9 | 10 | import 'dart:core' as $core; 11 | 12 | import 'package:grpc/service_api.dart' as $grpc; 13 | import 'contract.pb.dart' as $0; 14 | export 'contract.pb.dart'; 15 | 16 | class HospitalServerClient extends $grpc.Client { 17 | static final _$getHospitals = $grpc.ClientMethod<$0.Empty, $0.Hospitals>( 18 | '/HospitalServer/GetHospitals', 19 | ($0.Empty value) => value.writeToBuffer(), 20 | ($core.List<$core.int> value) => $0.Hospitals.fromBuffer(value)); 21 | static final _$searchHospitals = 22 | $grpc.ClientMethod<$0.SearchQuery, $0.Hospitals>( 23 | '/HospitalServer/SearchHospitals', 24 | ($0.SearchQuery value) => value.writeToBuffer(), 25 | ($core.List<$core.int> value) => $0.Hospitals.fromBuffer(value)); 26 | 27 | HospitalServerClient($grpc.ClientChannel channel, 28 | {$grpc.CallOptions? options, 29 | $core.Iterable<$grpc.ClientInterceptor>? interceptors}) 30 | : super(channel, options: options, interceptors: interceptors); 31 | 32 | $grpc.ResponseFuture<$0.Hospitals> getHospitals($0.Empty request, 33 | {$grpc.CallOptions? options}) { 34 | return $createUnaryCall(_$getHospitals, request, options: options); 35 | } 36 | 37 | $grpc.ResponseFuture<$0.Hospitals> searchHospitals($0.SearchQuery request, 38 | {$grpc.CallOptions? options}) { 39 | return $createUnaryCall(_$searchHospitals, request, options: options); 40 | } 41 | } 42 | 43 | abstract class HospitalServerServiceBase extends $grpc.Service { 44 | $core.String get $name => 'HospitalServer'; 45 | 46 | HospitalServerServiceBase() { 47 | $addMethod($grpc.ServiceMethod<$0.Empty, $0.Hospitals>( 48 | 'GetHospitals', 49 | getHospitals_Pre, 50 | false, 51 | false, 52 | ($core.List<$core.int> value) => $0.Empty.fromBuffer(value), 53 | ($0.Hospitals value) => value.writeToBuffer())); 54 | $addMethod($grpc.ServiceMethod<$0.SearchQuery, $0.Hospitals>( 55 | 'SearchHospitals', 56 | searchHospitals_Pre, 57 | false, 58 | false, 59 | ($core.List<$core.int> value) => $0.SearchQuery.fromBuffer(value), 60 | ($0.Hospitals value) => value.writeToBuffer())); 61 | } 62 | 63 | $async.Future<$0.Hospitals> getHospitals_Pre( 64 | $grpc.ServiceCall call, $async.Future<$0.Empty> request) async { 65 | return getHospitals(call, await request); 66 | } 67 | 68 | $async.Future<$0.Hospitals> searchHospitals_Pre( 69 | $grpc.ServiceCall call, $async.Future<$0.SearchQuery> request) async { 70 | return searchHospitals(call, await request); 71 | } 72 | 73 | $async.Future<$0.Hospitals> getHospitals( 74 | $grpc.ServiceCall call, $0.Empty request); 75 | $async.Future<$0.Hospitals> searchHospitals( 76 | $grpc.ServiceCall call, $0.SearchQuery request); 77 | } 78 | -------------------------------------------------------------------------------- /server/lib/src/protos/generated/contract.pbgrpc.dart: -------------------------------------------------------------------------------- 1 | /// 2 | // Generated code. Do not modify. 3 | // source: lib/src/protos/contract.proto 4 | // 5 | // @dart = 2.12 6 | // ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields 7 | 8 | import 'dart:async' as $async; 9 | 10 | import 'dart:core' as $core; 11 | 12 | import 'package:grpc/service_api.dart' as $grpc; 13 | import 'contract.pb.dart' as $0; 14 | export 'contract.pb.dart'; 15 | 16 | class HospitalServerClient extends $grpc.Client { 17 | static final _$getHospitals = $grpc.ClientMethod<$0.Empty, $0.Hospitals>( 18 | '/HospitalServer/GetHospitals', 19 | ($0.Empty value) => value.writeToBuffer(), 20 | ($core.List<$core.int> value) => $0.Hospitals.fromBuffer(value)); 21 | static final _$searchHospitals = 22 | $grpc.ClientMethod<$0.SearchQuery, $0.Hospitals>( 23 | '/HospitalServer/SearchHospitals', 24 | ($0.SearchQuery value) => value.writeToBuffer(), 25 | ($core.List<$core.int> value) => $0.Hospitals.fromBuffer(value)); 26 | 27 | HospitalServerClient($grpc.ClientChannel channel, 28 | {$grpc.CallOptions? options, 29 | $core.Iterable<$grpc.ClientInterceptor>? interceptors}) 30 | : super(channel, options: options, interceptors: interceptors); 31 | 32 | $grpc.ResponseFuture<$0.Hospitals> getHospitals($0.Empty request, 33 | {$grpc.CallOptions? options}) { 34 | return $createUnaryCall(_$getHospitals, request, options: options); 35 | } 36 | 37 | $grpc.ResponseFuture<$0.Hospitals> searchHospitals($0.SearchQuery request, 38 | {$grpc.CallOptions? options}) { 39 | return $createUnaryCall(_$searchHospitals, request, options: options); 40 | } 41 | } 42 | 43 | abstract class HospitalServerServiceBase extends $grpc.Service { 44 | $core.String get $name => 'HospitalServer'; 45 | 46 | HospitalServerServiceBase() { 47 | $addMethod($grpc.ServiceMethod<$0.Empty, $0.Hospitals>( 48 | 'GetHospitals', 49 | getHospitals_Pre, 50 | false, 51 | false, 52 | ($core.List<$core.int> value) => $0.Empty.fromBuffer(value), 53 | ($0.Hospitals value) => value.writeToBuffer())); 54 | $addMethod($grpc.ServiceMethod<$0.SearchQuery, $0.Hospitals>( 55 | 'SearchHospitals', 56 | searchHospitals_Pre, 57 | false, 58 | false, 59 | ($core.List<$core.int> value) => $0.SearchQuery.fromBuffer(value), 60 | ($0.Hospitals value) => value.writeToBuffer())); 61 | } 62 | 63 | $async.Future<$0.Hospitals> getHospitals_Pre( 64 | $grpc.ServiceCall call, $async.Future<$0.Empty> request) async { 65 | return getHospitals(call, await request); 66 | } 67 | 68 | $async.Future<$0.Hospitals> searchHospitals_Pre( 69 | $grpc.ServiceCall call, $async.Future<$0.SearchQuery> request) async { 70 | return searchHospitals(call, await request); 71 | } 72 | 73 | $async.Future<$0.Hospitals> getHospitals( 74 | $grpc.ServiceCall call, $0.Empty request); 75 | $async.Future<$0.Hospitals> searchHospitals( 76 | $grpc.ServiceCall call, $0.SearchQuery request); 77 | } 78 | -------------------------------------------------------------------------------- /app/lib/src/firebase_controllers.dart: -------------------------------------------------------------------------------- 1 | import 'package:firebase_auth/firebase_auth.dart'; 2 | import 'package:firebase_core/firebase_core.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 5 | import 'package:hospitals_riverpod/firebase_options.dart'; 6 | 7 | final firebaseInitProvider = FutureProvider( 8 | (ref) => Firebase.initializeApp( 9 | options: DefaultFirebaseOptions.currentPlatform, 10 | ), 11 | ); 12 | 13 | final firebaseAuthProvider = Provider((ref) => FirebaseAuth.instance); 14 | final userProvider = Provider((ref) { 15 | // just reload when user changes 16 | final _ = ref.watch(userStreamProvider); 17 | final user = ref.watch(firebaseAuthProvider).currentUser; 18 | return user; 19 | }); 20 | 21 | final userStreamProvider = StreamProvider( 22 | (ref) => FirebaseAuth.instance.authStateChanges(), 23 | ); 24 | 25 | class FirebaseInitPage extends ConsumerWidget { 26 | const FirebaseInitPage({ 27 | required this.homeWidget, 28 | Key? key, 29 | }) : super(key: key); 30 | 31 | final Widget homeWidget; 32 | 33 | @override 34 | Widget build(BuildContext context, WidgetRef ref) { 35 | final state = ref.watch(firebaseInitProvider); 36 | return state.when( 37 | data: (_) => homeWidget, 38 | error: (err, st) => Scaffold( 39 | appBar: AppBar( 40 | title: Text('Firebase Init Error'), 41 | ), 42 | body: Column( 43 | children: [ 44 | Text(err.toString()), 45 | SizedBox(height: 40), 46 | Text(st.toString()), 47 | ElevatedButton.icon( 48 | onPressed: () { 49 | ref.refresh(firebaseInitProvider); 50 | }, 51 | icon: Icon(Icons.refresh), 52 | label: Text('Try Again'), 53 | ), 54 | ], 55 | ), 56 | ), 57 | loading: () => Scaffold( 58 | appBar: AppBar( 59 | title: Text('Loading'), 60 | ), 61 | body: Center(child: CircularProgressIndicator()), 62 | ), 63 | ); 64 | } 65 | } 66 | 67 | class LoginLogoutIcon extends ConsumerWidget { 68 | const LoginLogoutIcon({Key? key}) : super(key: key); 69 | 70 | @override 71 | Widget build(BuildContext context, WidgetRef ref) { 72 | final streamState = ref.watch(userStreamProvider); 73 | return streamState.when( 74 | data: (user) { 75 | if (user == null) { 76 | return IconButton( 77 | onPressed: () { 78 | ref.read(firebaseAuthProvider).signInAnonymously(); 79 | }, 80 | icon: Icon(Icons.account_circle), 81 | ); 82 | } else { 83 | return IconButton( 84 | onPressed: () { 85 | ref.read(firebaseAuthProvider).signOut(); 86 | }, 87 | icon: Icon(Icons.exit_to_app), 88 | ); 89 | } 90 | }, 91 | error: (er, _) => IconButton( 92 | onPressed: () {}, 93 | icon: Icon(Icons.error), 94 | ), 95 | loading: () => Center( 96 | child: CircularProgressIndicator(), 97 | ), 98 | ); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /app/lib/src/map_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_map/flutter_map.dart'; 3 | import 'package:flutter_map_marker_cluster/flutter_map_marker_cluster.dart'; 4 | import 'package:hospitals_riverpod/src/generated/index.dart'; 5 | import 'base_map.dart'; 6 | import 'package:latlong2/latlong.dart'; 7 | 8 | class HospitalsMapView extends StatelessWidget { 9 | final List hospitals; 10 | 11 | HospitalsMapView({ 12 | Key? key, 13 | required this.hospitals, 14 | }) : super(key: key); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | final clusters = MarkerClusterLayerOptions( 19 | builder: (context, markers) { 20 | return FloatingActionButton( 21 | onPressed: null, 22 | child: Text(markers.length.toString()), 23 | ); 24 | }, 25 | size: Size(40, 40), 26 | maxClusterRadius: 70, 27 | fitBoundsOptions: FitBoundsOptions(padding: EdgeInsets.all(20.04)), 28 | markers: hospitals.map((e) => makeMarker(e, context)).toList(), 29 | ); 30 | 31 | return BaseMap( 32 | center: LatLng(0.60979, 37.686), 33 | zoom: 6, 34 | markerClusterLayerOptions: clusters, 35 | ); 36 | } 37 | 38 | Marker makeMarker(Hospital hospital, BuildContext context) { 39 | return Marker( 40 | point: LatLng( 41 | hospital.coordinate.latitude, 42 | hospital.coordinate.longitude, 43 | ), 44 | builder: (context) => MarkerBuilder( 45 | hospital: hospital, 46 | ), 47 | ); 48 | } 49 | } 50 | 51 | class MarkerBuilder extends StatelessWidget { 52 | final Hospital hospital; 53 | 54 | const MarkerBuilder({ 55 | Key? key, 56 | required this.hospital, 57 | }) : super(key: key); 58 | 59 | @override 60 | Widget build(BuildContext context) { 61 | return InkWell( 62 | child: Icon( 63 | Icons.location_pin, 64 | color: Colors.red, 65 | ), 66 | onTap: () { 67 | showBottomSheet( 68 | context: context, 69 | builder: (context) { 70 | return Container( 71 | height: MediaQuery.of(context).size.height * 0.33, 72 | width: double.infinity, 73 | color: Colors.black, 74 | child: Column( 75 | mainAxisSize: MainAxisSize.min, 76 | crossAxisAlignment: CrossAxisAlignment.center, 77 | children: [ 78 | IconButton( 79 | icon: Icon(Icons.arrow_downward), 80 | onPressed: () { 81 | Navigator.of(context).pop(); 82 | }, 83 | highlightColor: Colors.red, 84 | ), 85 | Divider( 86 | color: Colors.white, 87 | ), 88 | Padding( 89 | padding: const EdgeInsets.all(8.0), 90 | child: Text( 91 | hospital.name, 92 | style: Theme.of(context).textTheme.headline5, 93 | ), 94 | ), 95 | Padding( 96 | padding: const EdgeInsets.all(8.0), 97 | child: Text( 98 | hospital.location, 99 | style: Theme.of(context).textTheme.headline5, 100 | ), 101 | ), 102 | ], 103 | ), 104 | ); 105 | }, 106 | ); 107 | }, 108 | ); 109 | } 110 | } 111 | 112 | extension AsLatLng on Location { 113 | LatLng get asLatLng => LatLng(latitude, longitude); 114 | } 115 | -------------------------------------------------------------------------------- /server/lib/src/id_token_verifier.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:typed_data'; 3 | 4 | import 'package:basic_utils/basic_utils.dart'; 5 | import 'package:convert/convert.dart' show hex; 6 | import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart' as jwt; 7 | import 'package:dartz/dartz.dart'; 8 | import 'package:http/http.dart'; 9 | 10 | class FirebaseTokenVerifier { 11 | KeyIdResponse? idKeys; 12 | 13 | Future>> verifyToken( 14 | String idToken) async { 15 | if (idKeys == null) { 16 | return getKeyIds().then((value) { 17 | idKeys = value; 18 | }).then((_) => verifyToken(idToken)); 19 | } else { 20 | final expiry = idKeys!.expiry; 21 | final isExpired = DateTime.now().isAfter(expiry); 22 | if (isExpired) { 23 | idKeys = null; 24 | return verifyToken(idToken); 25 | } 26 | 27 | Error? error; 28 | Map? payload; 29 | 30 | for (var entry in idKeys!.keys.entries) { 31 | final publicKey = getPublicKeyFromCertificate(entry.value); 32 | try { 33 | final claims = jwt.JWT.verify(idToken, jwt.RSAPublicKey(publicKey)); 34 | payload = claims.payload as Map; 35 | } on jwt.JWTExpiredError { 36 | return Left('Token Expired'); 37 | } on jwt.JWTUndefinedError catch (e) { 38 | if (e.error is jwt.JWTExpiredError) { 39 | return Left('Token Expired'); 40 | } else { 41 | error = e.error; 42 | } 43 | } 44 | } 45 | if (payload == null) { 46 | throw Exception(error); 47 | } else { 48 | return Right(payload); 49 | } 50 | } 51 | } 52 | 53 | Future getKeyIds() async { 54 | final uri = ''' 55 | https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com'''; 56 | final response = await get(Uri.parse(uri)); 57 | final cacheControl = response.headers['cache-control']!; 58 | print(cacheControl); 59 | final maxAge = cacheControl.replaceAll('public, ', '').split(',').first; 60 | final expiryInSeconds = int.parse(maxAge.replaceAll('max-age=', '')); 61 | final keys = (jsonDecode(response.body) as Map) 62 | .map((key, value) => MapEntry(key, value as String)); 63 | return KeyIdResponse( 64 | expiry: DateTime.now().add(Duration(seconds: expiryInSeconds)), 65 | keys: keys, 66 | ); 67 | } 68 | 69 | Future>> getJwkKeys() async { 70 | final uri = 71 | "https://www.googleapis.com/service_accounts/v1/jwk/securetoken@system.gserviceaccount.com"; 72 | final response = await get(Uri.parse(uri)); 73 | final cacheControl = response.headers['cache-control']!; 74 | final maxAge = cacheControl.replaceAll('public, ', '').split(',').first; 75 | print(maxAge); 76 | final expiryInSeconds = int.parse(maxAge.replaceAll('max-age=', '')); 77 | final keys = (jsonDecode(response.body) as Map)['keys'] 78 | as List; 79 | 80 | return keys.map((e) => e as Map).toList(); 81 | } 82 | 83 | String getPublicKeyFromCertificate(String cert) { 84 | final parsed = X509Utils.x509CertificateFromPem(cert); 85 | final bytes = hex.decode( 86 | parsed.tbsCertificate.subjectPublicKeyInfo.bytes!, 87 | ); 88 | final key = CryptoUtils.rsaPublicKeyFromDERBytes( 89 | Uint8List.fromList(bytes), 90 | ); 91 | return CryptoUtils.encodeRSAPublicKeyToPemPkcs1(key); 92 | } 93 | } 94 | 95 | class KeyIdResponse { 96 | final DateTime expiry; 97 | final Map keys; 98 | KeyIdResponse({ 99 | required this.expiry, 100 | required this.keys, 101 | }); 102 | } 103 | -------------------------------------------------------------------------------- /app/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:developer'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 5 | import 'package:hospitals_riverpod/src/all_hospitals.dart'; 6 | import 'package:hospitals_riverpod/src/firebase_controllers.dart'; 7 | import 'package:hospitals_riverpod/src/search_hospitals_page.dart'; 8 | import 'package:hospitals_riverpod/src/stream_kenyan_hospitals.dart'; 9 | import 'package:logging/logging.dart'; 10 | import 'package:random_color_scheme/random_color_scheme.dart'; 11 | 12 | void main() { 13 | Logger.root.level = Level.ALL; 14 | Logger.root.onRecord.listen((LogRecord rec) { 15 | log( 16 | '${rec.loggerName}: ${rec.level.name}: ${rec.time}: ${rec.message}', 17 | ); 18 | }); 19 | 20 | runApp(ProviderScope(child: MyApp())); 21 | } 22 | 23 | class MyApp extends StatelessWidget { 24 | @override 25 | Widget build(BuildContext context) { 26 | final colorScheme = randomColorSchemeLight(shouldPrint: false); 27 | return MaterialApp( 28 | title: 'Flutter/Dart gRPC Demo', 29 | debugShowCheckedModeBanner: false, 30 | theme: ThemeData( 31 | colorScheme: colorScheme, 32 | appBarTheme: AppBarTheme( 33 | color: colorScheme.primary, 34 | actionsIconTheme: IconThemeData( 35 | color: colorScheme.secondary, 36 | ), 37 | ), 38 | ), 39 | home: FirebaseInitPage(homeWidget: EntryPoint()), 40 | ); 41 | } 42 | } 43 | 44 | class EntryPoint extends StatelessWidget { 45 | const EntryPoint({ 46 | Key? key, 47 | }) : super(key: key); 48 | 49 | @override 50 | Widget build(BuildContext context) { 51 | return Scaffold( 52 | appBar: AppBar( 53 | title: Text('gRPC Riverpod Example'), 54 | centerTitle: true, 55 | actions: [ 56 | LoginLogoutIcon(), 57 | ], 58 | actionsIconTheme: Theme.of(context).primaryIconTheme, 59 | ), 60 | body: Padding( 61 | padding: const EdgeInsets.all(8.0), 62 | child: Column( 63 | mainAxisAlignment: MainAxisAlignment.center, 64 | crossAxisAlignment: CrossAxisAlignment.center, 65 | children: [ 66 | ElevatedButton( 67 | onPressed: () { 68 | Navigator.of(context).push( 69 | MaterialPageRoute( 70 | builder: (context) => SearchHospitalsPage(), 71 | ), 72 | ); 73 | }, 74 | child: Container( 75 | alignment: Alignment.center, 76 | height: 20, 77 | child: Text('Search kenyan hospitals'), 78 | ), 79 | ), 80 | SizedBox(height: 20), 81 | ElevatedButton( 82 | onPressed: () { 83 | Navigator.of(context).push( 84 | MaterialPageRoute( 85 | builder: (context) => AllHospitalsPage(), 86 | ), 87 | ); 88 | }, 89 | child: Container( 90 | alignment: Alignment.center, 91 | height: 20, 92 | child: Text('All kenyan hospitals'), 93 | ), 94 | ), 95 | SizedBox(height: 20), 96 | ElevatedButton( 97 | onPressed: () { 98 | Navigator.of(context).push( 99 | MaterialPageRoute( 100 | builder: (context) => RandomNHospitalsPage(), 101 | ), 102 | ); 103 | }, 104 | child: Container( 105 | alignment: Alignment.center, 106 | height: 20, 107 | child: Text('Stream Kenyan Hospitals'), 108 | ), 109 | ), 110 | ], 111 | ), 112 | ), 113 | ); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /app/lib/src/search_hospitals_page.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 5 | import 'package:grpc/grpc_connection_interface.dart'; 6 | import 'package:hospitals_riverpod/src/generated/index.dart'; 7 | import 'common_widgets.dart'; 8 | import 'constants.dart'; 9 | 10 | final searchAheadProvider = Provider( 11 | (ref) => SearchAhead(ref.read(hostpitalClientProvider)), 12 | ); 13 | 14 | final hospitalsStreamProvider = StreamProvider( 15 | (ref) => ref.watch(searchAheadProvider).hospitalsStream, 16 | ); 17 | 18 | final userNameProvider = 19 | FutureProvider.family((ref, int id) async { 20 | return Future.value(id.toString()); 21 | }); 22 | 23 | class SearchAhead { 24 | // our grpc stub 25 | final HospitalServerClient stub; 26 | 27 | // getter to expose hospitals as a list of hospitals 28 | Stream> get hospitalsStream => 29 | _hospitalsStream.map((Hospitals e) => e.hospitals); 30 | 31 | // the text stream controller 32 | final _controller = StreamController(); 33 | 34 | // private variable that holds the state of the transformed stream 35 | late Stream _hospitalsStream; 36 | 37 | SearchAhead(this.stub) { 38 | // transform the textStream to Stream asyncMap. 39 | _hospitalsStream = _controller.stream.asyncMap((String query) async { 40 | Hospitals response = await stub.searchHospitals( 41 | SearchQuery( 42 | value: query, 43 | ), 44 | options: CallOptions(), 45 | ); 46 | return response; 47 | }); 48 | } 49 | 50 | // we add the typed values here using TextField onChanged 51 | void add(String keyword) async { 52 | _controller.add(keyword); 53 | } 54 | } 55 | 56 | class SearchHospitalsWidget extends ConsumerWidget { 57 | final ted = TextEditingController(); 58 | 59 | @override 60 | Widget build(BuildContext context, WidgetRef ref) { 61 | final searchProvider = ref.watch(searchAheadProvider); 62 | final resultState = ref.watch(hospitalsStreamProvider); 63 | 64 | return Padding( 65 | padding: const EdgeInsets.all(8.0), 66 | child: Column( 67 | children: [ 68 | TextField( 69 | // here we listen to keyboard changes via onChanged method 70 | // and add the words being typed into our streamController 71 | onChanged: (str) => searchProvider.add(str), 72 | autofocus: true, 73 | controller: ted, 74 | decoration: InputDecoration( 75 | suffix: Padding( 76 | padding: const EdgeInsets.all(4.0), 77 | child: Text( 78 | resultState.when( 79 | data: (d) => d.length.toString(), 80 | loading: () => '...', 81 | error: (_, __) => '!', 82 | ), 83 | style: TextStyle(fontWeight: FontWeight.bold), 84 | ), 85 | ), 86 | ), 87 | ), 88 | Expanded( 89 | child: resultState.when( 90 | data: (hospitals) => HospitalsListView(hospitals: hospitals), 91 | loading: () => LoadingView(msg: 'Searching for ${ted.text}'), 92 | error: (error, _) => ErrorView( 93 | error: error, 94 | msg: 'Error when searching for hospitals', 95 | retryFunction: () => searchProvider.add(ted.text), 96 | ), 97 | ), 98 | ), 99 | ], 100 | ), 101 | ); 102 | } 103 | } 104 | 105 | class SearchHospitalsPage extends StatelessWidget { 106 | const SearchHospitalsPage({Key? key}) : super(key: key); 107 | 108 | @override 109 | Widget build(BuildContext context) { 110 | return Scaffold( 111 | appBar: AppBar( 112 | title: Text('Search Kenyan Hospitals'), 113 | ), 114 | body: SearchHospitalsWidget(), 115 | ); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /server/README.md: -------------------------------------------------------------------------------- 1 | A sample command-line application with an entrypoint in `bin/`, library code 2 | in `lib/`, and example unit test in `test/`. 3 | 4 | Created from templates made available by Stagehand under a BSD-style 5 | [license](https://github.com/dart-lang/stagehand/blob/master/LICENSE). 6 | 7 | # Creating gRPC servers in Dart 8 | To see how to create a simple hospitals server in dart, kindly follow this article 9 | [Building an end-to-end system in dart using grpc* & flutter](https://bettdougie.medium.com/building-an-end-to-end-system-using-grpc-flutter-part-1-d23b2356ed28) 10 | 11 | It's a simple guide. This section adds more info on the article. 12 | 13 | ## Implementing ServerSide Interceptors 14 | 15 | gRPC contains interceptors which are executed before or after a request. 16 | 17 | According to the docs, an interceptor is typically a function/logic called before the corresponding `ServiceMethod` invocation. If the interceptor returns a `GrpcError`, the error will be returned as a response and `ServiceMethod` wouldn't be called. If the interceptor throws `Exception`, `GrpcError.internal` with `exception.toString()` will be returned. If the interceptor returns null, the corresponding `ServiceMethod` of `Service` will be called. 18 | 19 | ### Interceptor Example UseCases 20 | 1. Handling Authentication 21 | 2. Handling Authorization 22 | 3. Modifying metadata/Adding custom claims, etc 23 | 4. Logging Requests 24 | 25 | In our case, since we added Firebase Authentication to the client, we'll be adding authentication to ensure the validity of the jwt token sent by the client. This is to ensure that all requests are from Authenticated Firebase Clients. We'll also create a Logging Interceptor to log our requests. 26 | 27 | ## Interceptor Syntax 28 | ```dart 29 | typedef Interceptor = FutureOr Function(ServiceCall call, ServiceMethod method); 30 | ``` 31 | 32 | Simply explained, we can create an interceptor by creating a function that either returns a GrpcError or null. If the interceptor returns a `GrpcError`, the method will fail. 33 | 34 | This is what we'll use to ensure all our requests are authenticated. 35 | 36 | Before that, let's start with a simple interceptor to log all incoming requests. It's syntax is fairly simple. Here we'll be extracting a couple of information from the clientMetadata. 37 | The client metadata gives us info like the ip address of the request, the method call, the path, and all other metadata transmitted by the client. 38 | In the metadata is where things like tokens & other info are stored as a key-value dictionary. 39 | 40 | ```dart 41 | Future loggingInterceptor( 42 | ServiceCall call, 43 | ServiceMethod method, 44 | ) async { 45 | final dateTime = DateTime.now(); 46 | final clientMetadata = call.clientMetadata ?? {}; 47 | final authority = clientMetadata[':authority']; 48 | final methodName = clientMetadata[':path']; 49 | final method = clientMetadata[':method']; 50 | final userAgent = clientMetadata['user-agent']; 51 | 52 | logger.info('$authority - - [$dateTime] $method $methodName $userAgent'); 53 | return null; 54 | } 55 | ``` 56 | 57 | When starting the server, we then include the `loggingInterceptor` in the list of interceptors. 58 | ```dart 59 | final hospitalsData = await Utils.readHospitalsFromCSV(); 60 | 61 | final interceptors = [ 62 | loggingInterceptor, 63 | ]; 64 | 65 | final server = Server( 66 | [HospitalServer(hospitalData: hospitalsData)], 67 | interceptors, 68 | ); 69 | 70 | final ip = InternetAddress.anyIPv4; 71 | 72 | await server.serve(port: intPort, address: ip); 73 | ``` 74 | 75 | When a request arrives, we'll get a log printed by the loggingInterceptor 76 | 77 | `0.0.0.0:3001 - - [2022-09-03 10:34:56.962237] POST /HospitalServer/SearchHospitals dart-grpc/2.0.0` 78 | 79 | ## Auth Interceptor Example 80 | Let's say we have a function that just verifies the token. 81 | 82 | ```dart 83 | Future>> verifyToken( 84 | String idToken, 85 | ) async { 86 | // ... 87 | // returns claims if token 88 | return FirebaseTokenVerifier().verifyToken(idToken); 89 | } 90 | ``` 91 | 92 | We can create an authentication that takes the response from verify token and return either `GrpcError.unauthenticated` or null; 93 | 94 | ```dart 95 | Future authInterceptor( 96 | ServiceCall call, 97 | ServiceMethod method, 98 | ) async { 99 | final metadata = call.clientMetadata ?? {}; 100 | final idToken = metadata['token']; 101 | if (idToken == null) { 102 | return GrpcError.unauthenticated('Missing Auth Token'); 103 | } 104 | final response = await verifyToken(idToken); 105 | return response.fold( 106 | (l) => GrpcError.unauthenticated(l), 107 | (claims) { 108 | metadata['user_id'] = claims['user_id']; 109 | return; 110 | }, 111 | ); 112 | } 113 | ``` 114 | If we add it to the server, all requests with invalid tokens will be stopped and the response will be `unauthenticated`. 115 | If the token is validated however, the user_id will be added to the `clientMetadata`, so successive interceptors can access this value in the metadata. 116 | 117 | ```dart 118 | final hospitalsData = await Utils.readHospitalsFromCSV(); 119 | 120 | final interceptors = [ 121 | loggingInterceptor, 122 | authInterceptor, 123 | ]; 124 | 125 | final server = Server( 126 | [HospitalServer(hospitalData: hospitalsData)], 127 | interceptors, 128 | ); 129 | 130 | final ip = InternetAddress.anyIPv4; 131 | 132 | await server.serve(port: intPort, address: ip); 133 | ``` 134 | 135 | This is how we can use interceptors for authentication. -------------------------------------------------------------------------------- /app/README.md: -------------------------------------------------------------------------------- 1 | # hospitals_riverpod 2 | 3 | A new Flutter project. 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Flutter application. 8 | 9 | A few resources to get you started if this is your first Flutter project: 10 | 11 | - [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) 13 | 14 | For help getting started with Flutter, view our 15 | [online documentation](https://flutter.dev/docs), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | 18 | ## This example displays how to create a gRPC Flutter Client connecting to a gRPC dart client. 19 | 20 | It's been refactored to use Firebase Authentication. 21 | 22 | To be able to run this example, please use the https://firebase.flutter.dev/docs/cli/ for ease of installation. 23 | 24 | It automates the configuration of Flutter Projects which depend on Firebase. This gives us easier configuration options. 25 | 26 | After finishing the firebase configuration, you can start the dart server contained in the parent folder called server/ 27 | Once you start the server, you should be able to experiment with this project freely. 28 | 29 | ### ClientSide gRPC Interceptors 30 | 31 | An interceptor is called before the corresponding `ServiceMethod` invocation. If the interceptor returns a `GrpcError`, the error will be returned as a response and `ServiceMethod` wouldn't be called. If the interceptor throws `Exception`, `GrpcError.internal` with `exception.toString()` will be returned. If the interceptor returns null, the corresponding `ServiceMethod` of `Service` will be called. 32 | 33 | `ClientInterceptors` intercepts client calls before they are executed. 34 | 35 | 36 | This means every time a request is made to the server, the logic embedded in the client side interceptor is run. Most of the time interceptors are for 37 | - Modify request metadata e.g add Authentication Tokens, add request counts, etc 38 | - Log Requests. 39 | - Count the number of requests made. 40 | 41 | When gRPC generates the ClientStub, it enables us to pass a list of interceptors which extend `ClientInterceptor` class. 42 | 43 | Let's create a LoggingInterceptor. For this, we want to print all method calls going to the server. We first have to extend the ClientInterceptor class so that we can add our own logic into the interceptor 44 | 45 | ```dart 46 | class LoggingInterceptor implements ClientInterceptor { 47 | @override 48 | ResponseStream interceptStreaming( 49 | ClientMethod method, 50 | Stream requests, 51 | CallOptions options, 52 | ClientStreamingInvoker invoker, 53 | ) { 54 | // TODO: implement interceptStreaming 55 | throw UnimplementedError(); 56 | } 57 | 58 | @override 59 | ResponseFuture interceptUnary( 60 | ClientMethod method, 61 | Q request, 62 | CallOptions options, 63 | ClientUnaryInvoker invoker, 64 | ) { 65 | // TODO: implement interceptUnary 66 | throw UnimplementedError(); 67 | } 68 | } 69 | ``` 70 | 71 | To know the method call, the `ClientMethod` class contains a parameter called path which represents the name of the method to be invoked on the server. We'll just print the method name here and call the method to proceed with its functionality. 72 | 73 | ```dart 74 | @override 75 | ResponseFuture interceptUnary( 76 | ClientMethod method, 77 | Q request, 78 | CallOptions options, 79 | ClientUnaryInvoker invoker, 80 | ) { 81 | logger.info(method.path); 82 | logger.info(options.metadata); 83 | return super.interceptUnary(method, request, options, invoker); 84 | } 85 | ``` 86 | 87 | All method calls after this will print the name and request metadata contained in the request call. 88 | 89 | ### Adding the interceptor to the client stub 90 | Once we have defined an interceptor, we pass it into the ClientStub constructor. 91 | 92 | ```dart 93 | final clientStub = HospitalServerClient( 94 | ClientChannel( 95 | '0.0.0.0', // connect to localhost. Where it's served. 96 | port: 3001, 97 | options: ChannelOptions( 98 | credentials: ChannelCredentials.insecure(), // transmit unencrypted data., 99 | ), // pass the channelOptions above. 100 | ), 101 | interceptors: [ 102 | // this logs the requests 103 | RequestLoggingInterceptor(), 104 | // this injects the firebase token into the request call 105 | AuthMetadataInterceptor(firebaseAuth: FirebaseAuth.instance), 106 | ], 107 | ); 108 | ``` 109 | 110 | ## Metadata Modifying Interceptor 111 | Let's say you use Firebase Authentication and you want to send the firebase idToken to the backend server since the backend can validate Firebase Claims and know who's the user sending this request. 112 | We can either have all calls to gRPC call the getIDToken method and manually inject the idToken into the call options then make the call. Or we can create a single interceptor that does that on all calls either Streaming/Unary calls. 113 | 114 | We'll first extend the ClientInterceptor class as in the logging example. We'll also need the FirebaseAuth instance in order to get the current user. 115 | ```dart 116 | 117 | import 'package:firebase_auth/firebase_auth.dart'; // firebase 118 | import 'package:grpc/grpc.dart'; 119 | 120 | class AuthInterceptor implements ClientInterceptor { 121 | final FirebaseAuth firebaseAuth; 122 | AuthInterceptor({ 123 | required this.firebaseAuth, 124 | }); 125 | 126 | @override 127 | ResponseStream interceptStreaming( 128 | ClientMethod method, 129 | Stream requests, 130 | CallOptions options, 131 | ClientStreamingInvoker invoker, 132 | ) { 133 | // TODO: implement interceptStreaming 134 | throw UnimplementedError(); 135 | } 136 | 137 | @override 138 | ResponseFuture interceptUnary( 139 | ClientMethod method, 140 | Q request, 141 | CallOptions options, 142 | ClientUnaryInvoker invoker, 143 | ) { 144 | // TODO: implement interceptUnary 145 | throw UnimplementedError(); 146 | } 147 | } 148 | 149 | ``` 150 | The challenge with implementing this is that gRPC interceptors cannot be asynchronous. Luckily for us, the gRPC CallOptions exposes a parameter called providers, which are asynchronous metadata modifying functions. It syntax goes like 151 | ```dart 152 | Future _injectToken(Map metadata, String uri) async { 153 | metadata['token'] = 'user token'; 154 | metadata['request_time'] = DateTime.now().toIso8601String(); 155 | } 156 | ``` 157 | In our case, we'll inject the token as below 158 | ```dart 159 | Future _injectToken(Map metadata, String uri) async { 160 | final user = firebaseAuth.currentUser; 161 | if (user != null) { 162 | final token = await user.getIdToken(); 163 | metadata['token'] = token; 164 | } 165 | } 166 | ``` 167 | 168 | Once we've defined the metadata modifying function, we create a copy of the outgoing call options and call the super method we're intercepting. 169 | 170 | ```dart 171 | @override 172 | ResponseStream interceptStreaming( 173 | ClientMethod method, 174 | Stream requests, 175 | CallOptions options, 176 | ClientStreamingInvoker invoker, 177 | ) { 178 | final modifiedOptions = options.mergedWith( 179 | CallOptions( 180 | providers: [ 181 | _injectToken, // method signatures match, so we should be ok 182 | ], 183 | ), 184 | ); 185 | return super.interceptStreaming(method, requests, modifiedOptions, invoker); 186 | } 187 | 188 | @override 189 | ResponseFuture interceptUnary( 190 | ClientMethod method, 191 | Q request, 192 | CallOptions options, 193 | ClientUnaryInvoker invoker, 194 | ) { 195 | final modifiedOptions = options.mergedWith( 196 | CallOptions( 197 | providers: [ 198 | (Map metadata, String uri) { 199 | return _injectToken(metadata, uri); 200 | }, 201 | ], 202 | ), 203 | ); 204 | return super.interceptUnary(method, request, modifiedOptions, invoker); 205 | } 206 | ``` 207 | I hope that makes us understand how to handle inject metadata that come from asynchronous source. 208 | 209 | To look into the server-side interceptors, the guide will be in the README contained in the `server/` folder -------------------------------------------------------------------------------- /app/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | animated_stack_widget: 5 | dependency: transitive 6 | description: 7 | name: animated_stack_widget 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "0.0.4" 11 | archive: 12 | dependency: transitive 13 | description: 14 | name: archive 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "3.3.1" 18 | async: 19 | dependency: transitive 20 | description: 21 | name: async 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "2.8.2" 25 | boolean_selector: 26 | dependency: transitive 27 | description: 28 | name: boolean_selector 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "2.1.0" 32 | characters: 33 | dependency: transitive 34 | description: 35 | name: characters 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "1.2.0" 39 | charcode: 40 | dependency: transitive 41 | description: 42 | name: charcode 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.3.1" 46 | clock: 47 | dependency: transitive 48 | description: 49 | name: clock 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "1.1.0" 53 | collection: 54 | dependency: transitive 55 | description: 56 | name: collection 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "1.16.0" 60 | crypto: 61 | dependency: transitive 62 | description: 63 | name: crypto 64 | url: "https://pub.dartlang.org" 65 | source: hosted 66 | version: "3.0.1" 67 | fake_async: 68 | dependency: transitive 69 | description: 70 | name: fake_async 71 | url: "https://pub.dartlang.org" 72 | source: hosted 73 | version: "1.3.0" 74 | firebase_auth: 75 | dependency: "direct main" 76 | description: 77 | name: firebase_auth 78 | url: "https://pub.dartlang.org" 79 | source: hosted 80 | version: "3.3.8" 81 | firebase_auth_platform_interface: 82 | dependency: transitive 83 | description: 84 | name: firebase_auth_platform_interface 85 | url: "https://pub.dartlang.org" 86 | source: hosted 87 | version: "6.2.0" 88 | firebase_auth_web: 89 | dependency: transitive 90 | description: 91 | name: firebase_auth_web 92 | url: "https://pub.dartlang.org" 93 | source: hosted 94 | version: "3.3.8" 95 | firebase_core: 96 | dependency: "direct main" 97 | description: 98 | name: firebase_core 99 | url: "https://pub.dartlang.org" 100 | source: hosted 101 | version: "1.12.0" 102 | firebase_core_platform_interface: 103 | dependency: transitive 104 | description: 105 | name: firebase_core_platform_interface 106 | url: "https://pub.dartlang.org" 107 | source: hosted 108 | version: "4.2.4" 109 | firebase_core_web: 110 | dependency: transitive 111 | description: 112 | name: firebase_core_web 113 | url: "https://pub.dartlang.org" 114 | source: hosted 115 | version: "1.6.0" 116 | fixnum: 117 | dependency: transitive 118 | description: 119 | name: fixnum 120 | url: "https://pub.dartlang.org" 121 | source: hosted 122 | version: "1.0.1" 123 | flutter: 124 | dependency: "direct main" 125 | description: flutter 126 | source: sdk 127 | version: "0.0.0" 128 | flutter_map: 129 | dependency: "direct main" 130 | description: 131 | name: flutter_map 132 | url: "https://pub.dartlang.org" 133 | source: hosted 134 | version: "0.13.1" 135 | flutter_map_marker_cluster: 136 | dependency: "direct main" 137 | description: 138 | name: flutter_map_marker_cluster 139 | url: "https://pub.dartlang.org" 140 | source: hosted 141 | version: "0.4.4" 142 | flutter_map_marker_popup: 143 | dependency: transitive 144 | description: 145 | name: flutter_map_marker_popup 146 | url: "https://pub.dartlang.org" 147 | source: hosted 148 | version: "2.1.2" 149 | flutter_riverpod: 150 | dependency: "direct main" 151 | description: 152 | name: flutter_riverpod 153 | url: "https://pub.dartlang.org" 154 | source: hosted 155 | version: "1.0.4" 156 | flutter_test: 157 | dependency: "direct dev" 158 | description: flutter 159 | source: sdk 160 | version: "0.0.0" 161 | flutter_web_plugins: 162 | dependency: transitive 163 | description: flutter 164 | source: sdk 165 | version: "0.0.0" 166 | googleapis_auth: 167 | dependency: transitive 168 | description: 169 | name: googleapis_auth 170 | url: "https://pub.dartlang.org" 171 | source: hosted 172 | version: "1.3.1" 173 | grpc: 174 | dependency: "direct main" 175 | description: 176 | name: grpc 177 | url: "https://pub.dartlang.org" 178 | source: hosted 179 | version: "3.0.2" 180 | hsluv: 181 | dependency: transitive 182 | description: 183 | name: hsluv 184 | url: "https://pub.dartlang.org" 185 | source: hosted 186 | version: "1.1.2" 187 | http: 188 | dependency: transitive 189 | description: 190 | name: http 191 | url: "https://pub.dartlang.org" 192 | source: hosted 193 | version: "0.13.3" 194 | http2: 195 | dependency: transitive 196 | description: 197 | name: http2 198 | url: "https://pub.dartlang.org" 199 | source: hosted 200 | version: "2.0.0" 201 | http_parser: 202 | dependency: transitive 203 | description: 204 | name: http_parser 205 | url: "https://pub.dartlang.org" 206 | source: hosted 207 | version: "4.0.1" 208 | intl: 209 | dependency: transitive 210 | description: 211 | name: intl 212 | url: "https://pub.dartlang.org" 213 | source: hosted 214 | version: "0.17.0" 215 | js: 216 | dependency: transitive 217 | description: 218 | name: js 219 | url: "https://pub.dartlang.org" 220 | source: hosted 221 | version: "0.6.4" 222 | latlong2: 223 | dependency: transitive 224 | description: 225 | name: latlong2 226 | url: "https://pub.dartlang.org" 227 | source: hosted 228 | version: "0.8.1" 229 | lists: 230 | dependency: transitive 231 | description: 232 | name: lists 233 | url: "https://pub.dartlang.org" 234 | source: hosted 235 | version: "1.0.1" 236 | logging: 237 | dependency: "direct main" 238 | description: 239 | name: logging 240 | url: "https://pub.dartlang.org" 241 | source: hosted 242 | version: "1.0.2" 243 | matcher: 244 | dependency: transitive 245 | description: 246 | name: matcher 247 | url: "https://pub.dartlang.org" 248 | source: hosted 249 | version: "0.12.11" 250 | material_color_utilities: 251 | dependency: transitive 252 | description: 253 | name: material_color_utilities 254 | url: "https://pub.dartlang.org" 255 | source: hosted 256 | version: "0.1.4" 257 | meta: 258 | dependency: transitive 259 | description: 260 | name: meta 261 | url: "https://pub.dartlang.org" 262 | source: hosted 263 | version: "1.7.0" 264 | mgrs_dart: 265 | dependency: transitive 266 | description: 267 | name: mgrs_dart 268 | url: "https://pub.dartlang.org" 269 | source: hosted 270 | version: "2.0.0" 271 | modal_bottom_sheet: 272 | dependency: "direct main" 273 | description: 274 | name: modal_bottom_sheet 275 | url: "https://pub.dartlang.org" 276 | source: hosted 277 | version: "2.0.1" 278 | path: 279 | dependency: transitive 280 | description: 281 | name: path 282 | url: "https://pub.dartlang.org" 283 | source: hosted 284 | version: "1.8.1" 285 | pedantic: 286 | dependency: transitive 287 | description: 288 | name: pedantic 289 | url: "https://pub.dartlang.org" 290 | source: hosted 291 | version: "1.11.1" 292 | plugin_platform_interface: 293 | dependency: transitive 294 | description: 295 | name: plugin_platform_interface 296 | url: "https://pub.dartlang.org" 297 | source: hosted 298 | version: "2.1.2" 299 | positioned_tap_detector_2: 300 | dependency: transitive 301 | description: 302 | name: positioned_tap_detector_2 303 | url: "https://pub.dartlang.org" 304 | source: hosted 305 | version: "1.0.4" 306 | proj4dart: 307 | dependency: transitive 308 | description: 309 | name: proj4dart 310 | url: "https://pub.dartlang.org" 311 | source: hosted 312 | version: "2.1.0" 313 | protobuf: 314 | dependency: "direct main" 315 | description: 316 | name: protobuf 317 | url: "https://pub.dartlang.org" 318 | source: hosted 319 | version: "2.0.1" 320 | quiver: 321 | dependency: transitive 322 | description: 323 | name: quiver 324 | url: "https://pub.dartlang.org" 325 | source: hosted 326 | version: "3.1.0" 327 | random_color_scheme: 328 | dependency: "direct main" 329 | description: 330 | name: random_color_scheme 331 | url: "https://pub.dartlang.org" 332 | source: hosted 333 | version: "0.1.3" 334 | riverpod: 335 | dependency: transitive 336 | description: 337 | name: riverpod 338 | url: "https://pub.dartlang.org" 339 | source: hosted 340 | version: "1.0.3" 341 | sky_engine: 342 | dependency: transitive 343 | description: flutter 344 | source: sdk 345 | version: "0.0.99" 346 | source_span: 347 | dependency: transitive 348 | description: 349 | name: source_span 350 | url: "https://pub.dartlang.org" 351 | source: hosted 352 | version: "1.8.2" 353 | stack_trace: 354 | dependency: transitive 355 | description: 356 | name: stack_trace 357 | url: "https://pub.dartlang.org" 358 | source: hosted 359 | version: "1.10.0" 360 | state_notifier: 361 | dependency: transitive 362 | description: 363 | name: state_notifier 364 | url: "https://pub.dartlang.org" 365 | source: hosted 366 | version: "0.7.2+1" 367 | stream_channel: 368 | dependency: transitive 369 | description: 370 | name: stream_channel 371 | url: "https://pub.dartlang.org" 372 | source: hosted 373 | version: "2.1.0" 374 | string_scanner: 375 | dependency: transitive 376 | description: 377 | name: string_scanner 378 | url: "https://pub.dartlang.org" 379 | source: hosted 380 | version: "1.1.0" 381 | term_glyph: 382 | dependency: transitive 383 | description: 384 | name: term_glyph 385 | url: "https://pub.dartlang.org" 386 | source: hosted 387 | version: "1.2.0" 388 | test_api: 389 | dependency: transitive 390 | description: 391 | name: test_api 392 | url: "https://pub.dartlang.org" 393 | source: hosted 394 | version: "0.4.9" 395 | transparent_image: 396 | dependency: transitive 397 | description: 398 | name: transparent_image 399 | url: "https://pub.dartlang.org" 400 | source: hosted 401 | version: "2.0.0" 402 | tuple: 403 | dependency: transitive 404 | description: 405 | name: tuple 406 | url: "https://pub.dartlang.org" 407 | source: hosted 408 | version: "2.0.0" 409 | typed_data: 410 | dependency: transitive 411 | description: 412 | name: typed_data 413 | url: "https://pub.dartlang.org" 414 | source: hosted 415 | version: "1.3.0" 416 | unicode: 417 | dependency: transitive 418 | description: 419 | name: unicode 420 | url: "https://pub.dartlang.org" 421 | source: hosted 422 | version: "0.3.1" 423 | vector_math: 424 | dependency: transitive 425 | description: 426 | name: vector_math 427 | url: "https://pub.dartlang.org" 428 | source: hosted 429 | version: "2.1.2" 430 | wkt_parser: 431 | dependency: transitive 432 | description: 433 | name: wkt_parser 434 | url: "https://pub.dartlang.org" 435 | source: hosted 436 | version: "2.0.0" 437 | sdks: 438 | dart: ">=2.17.0-0 <3.0.0" 439 | flutter: ">=3.0.0" 440 | -------------------------------------------------------------------------------- /server/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | _fe_analyzer_shared: 5 | dependency: transitive 6 | description: 7 | name: _fe_analyzer_shared 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "19.0.0" 11 | analyzer: 12 | dependency: transitive 13 | description: 14 | name: analyzer 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "1.3.0" 18 | archive: 19 | dependency: transitive 20 | description: 21 | name: archive 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "3.1.2" 25 | args: 26 | dependency: transitive 27 | description: 28 | name: args 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "2.0.0" 32 | async: 33 | dependency: transitive 34 | description: 35 | name: async 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "2.5.0" 39 | basic_utils: 40 | dependency: "direct main" 41 | description: 42 | name: basic_utils 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "5.2.0" 46 | boolean_selector: 47 | dependency: transitive 48 | description: 49 | name: boolean_selector 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "2.1.0" 53 | charcode: 54 | dependency: transitive 55 | description: 56 | name: charcode 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "1.2.0" 60 | cli_util: 61 | dependency: transitive 62 | description: 63 | name: cli_util 64 | url: "https://pub.dartlang.org" 65 | source: hosted 66 | version: "0.3.0" 67 | clock: 68 | dependency: transitive 69 | description: 70 | name: clock 71 | url: "https://pub.dartlang.org" 72 | source: hosted 73 | version: "1.1.0" 74 | collection: 75 | dependency: transitive 76 | description: 77 | name: collection 78 | url: "https://pub.dartlang.org" 79 | source: hosted 80 | version: "1.15.0" 81 | convert: 82 | dependency: transitive 83 | description: 84 | name: convert 85 | url: "https://pub.dartlang.org" 86 | source: hosted 87 | version: "3.0.2" 88 | coverage: 89 | dependency: transitive 90 | description: 91 | name: coverage 92 | url: "https://pub.dartlang.org" 93 | source: hosted 94 | version: "1.0.2" 95 | crypto: 96 | dependency: transitive 97 | description: 98 | name: crypto 99 | url: "https://pub.dartlang.org" 100 | source: hosted 101 | version: "3.0.1" 102 | csv: 103 | dependency: "direct main" 104 | description: 105 | name: csv 106 | url: "https://pub.dartlang.org" 107 | source: hosted 108 | version: "5.0.0" 109 | dart_jsonwebtoken: 110 | dependency: "direct main" 111 | description: 112 | name: dart_jsonwebtoken 113 | url: "https://pub.dartlang.org" 114 | source: hosted 115 | version: "2.4.2" 116 | dart_jts: 117 | dependency: "direct main" 118 | description: 119 | name: dart_jts 120 | url: "https://pub.dartlang.org" 121 | source: hosted 122 | version: "0.2.1" 123 | dartz: 124 | dependency: "direct main" 125 | description: 126 | name: dartz 127 | url: "https://pub.dartlang.org" 128 | source: hosted 129 | version: "0.10.0" 130 | file: 131 | dependency: transitive 132 | description: 133 | name: file 134 | url: "https://pub.dartlang.org" 135 | source: hosted 136 | version: "6.1.0" 137 | fixnum: 138 | dependency: transitive 139 | description: 140 | name: fixnum 141 | url: "https://pub.dartlang.org" 142 | source: hosted 143 | version: "1.0.0" 144 | freezed_annotation: 145 | dependency: transitive 146 | description: 147 | name: freezed_annotation 148 | url: "https://pub.dartlang.org" 149 | source: hosted 150 | version: "0.14.1" 151 | glob: 152 | dependency: transitive 153 | description: 154 | name: glob 155 | url: "https://pub.dartlang.org" 156 | source: hosted 157 | version: "2.0.1" 158 | googleapis_auth: 159 | dependency: transitive 160 | description: 161 | name: googleapis_auth 162 | url: "https://pub.dartlang.org" 163 | source: hosted 164 | version: "1.1.0" 165 | grpc: 166 | dependency: "direct main" 167 | description: 168 | name: grpc 169 | url: "https://pub.dartlang.org" 170 | source: hosted 171 | version: "3.0.0" 172 | http: 173 | dependency: transitive 174 | description: 175 | name: http 176 | url: "https://pub.dartlang.org" 177 | source: hosted 178 | version: "0.13.5" 179 | http2: 180 | dependency: transitive 181 | description: 182 | name: http2 183 | url: "https://pub.dartlang.org" 184 | source: hosted 185 | version: "2.0.0" 186 | http_multi_server: 187 | dependency: transitive 188 | description: 189 | name: http_multi_server 190 | url: "https://pub.dartlang.org" 191 | source: hosted 192 | version: "3.0.0" 193 | http_parser: 194 | dependency: transitive 195 | description: 196 | name: http_parser 197 | url: "https://pub.dartlang.org" 198 | source: hosted 199 | version: "4.0.0" 200 | intl: 201 | dependency: transitive 202 | description: 203 | name: intl 204 | url: "https://pub.dartlang.org" 205 | source: hosted 206 | version: "0.17.0" 207 | io: 208 | dependency: transitive 209 | description: 210 | name: io 211 | url: "https://pub.dartlang.org" 212 | source: hosted 213 | version: "1.0.0" 214 | js: 215 | dependency: transitive 216 | description: 217 | name: js 218 | url: "https://pub.dartlang.org" 219 | source: hosted 220 | version: "0.6.3" 221 | json_annotation: 222 | dependency: transitive 223 | description: 224 | name: json_annotation 225 | url: "https://pub.dartlang.org" 226 | source: hosted 227 | version: "4.6.0" 228 | logging: 229 | dependency: "direct main" 230 | description: 231 | name: logging 232 | url: "https://pub.dartlang.org" 233 | source: hosted 234 | version: "1.0.2" 235 | matcher: 236 | dependency: transitive 237 | description: 238 | name: matcher 239 | url: "https://pub.dartlang.org" 240 | source: hosted 241 | version: "0.12.10" 242 | meta: 243 | dependency: transitive 244 | description: 245 | name: meta 246 | url: "https://pub.dartlang.org" 247 | source: hosted 248 | version: "1.8.0" 249 | mime: 250 | dependency: transitive 251 | description: 252 | name: mime 253 | url: "https://pub.dartlang.org" 254 | source: hosted 255 | version: "1.0.0" 256 | node_preamble: 257 | dependency: transitive 258 | description: 259 | name: node_preamble 260 | url: "https://pub.dartlang.org" 261 | source: hosted 262 | version: "2.0.0" 263 | package_config: 264 | dependency: transitive 265 | description: 266 | name: package_config 267 | url: "https://pub.dartlang.org" 268 | source: hosted 269 | version: "2.0.0" 270 | path: 271 | dependency: transitive 272 | description: 273 | name: path 274 | url: "https://pub.dartlang.org" 275 | source: hosted 276 | version: "1.8.0" 277 | pedantic: 278 | dependency: "direct dev" 279 | description: 280 | name: pedantic 281 | url: "https://pub.dartlang.org" 282 | source: hosted 283 | version: "1.11.0" 284 | pointycastle: 285 | dependency: transitive 286 | description: 287 | name: pointycastle 288 | url: "https://pub.dartlang.org" 289 | source: hosted 290 | version: "3.6.1" 291 | pool: 292 | dependency: transitive 293 | description: 294 | name: pool 295 | url: "https://pub.dartlang.org" 296 | source: hosted 297 | version: "1.5.0" 298 | protobuf: 299 | dependency: "direct main" 300 | description: 301 | name: protobuf 302 | url: "https://pub.dartlang.org" 303 | source: hosted 304 | version: "2.0.0" 305 | pub_semver: 306 | dependency: transitive 307 | description: 308 | name: pub_semver 309 | url: "https://pub.dartlang.org" 310 | source: hosted 311 | version: "2.0.0" 312 | riverpod: 313 | dependency: "direct dev" 314 | description: 315 | name: riverpod 316 | url: "https://pub.dartlang.org" 317 | source: hosted 318 | version: "0.14.0" 319 | shelf: 320 | dependency: transitive 321 | description: 322 | name: shelf 323 | url: "https://pub.dartlang.org" 324 | source: hosted 325 | version: "1.1.0" 326 | shelf_packages_handler: 327 | dependency: transitive 328 | description: 329 | name: shelf_packages_handler 330 | url: "https://pub.dartlang.org" 331 | source: hosted 332 | version: "3.0.0" 333 | shelf_static: 334 | dependency: transitive 335 | description: 336 | name: shelf_static 337 | url: "https://pub.dartlang.org" 338 | source: hosted 339 | version: "1.0.0" 340 | shelf_web_socket: 341 | dependency: transitive 342 | description: 343 | name: shelf_web_socket 344 | url: "https://pub.dartlang.org" 345 | source: hosted 346 | version: "1.0.1" 347 | source_map_stack_trace: 348 | dependency: transitive 349 | description: 350 | name: source_map_stack_trace 351 | url: "https://pub.dartlang.org" 352 | source: hosted 353 | version: "2.1.0" 354 | source_maps: 355 | dependency: transitive 356 | description: 357 | name: source_maps 358 | url: "https://pub.dartlang.org" 359 | source: hosted 360 | version: "0.10.10" 361 | source_span: 362 | dependency: transitive 363 | description: 364 | name: source_span 365 | url: "https://pub.dartlang.org" 366 | source: hosted 367 | version: "1.8.1" 368 | stack_trace: 369 | dependency: transitive 370 | description: 371 | name: stack_trace 372 | url: "https://pub.dartlang.org" 373 | source: hosted 374 | version: "1.10.0" 375 | state_notifier: 376 | dependency: transitive 377 | description: 378 | name: state_notifier 379 | url: "https://pub.dartlang.org" 380 | source: hosted 381 | version: "0.7.0" 382 | stream_channel: 383 | dependency: transitive 384 | description: 385 | name: stream_channel 386 | url: "https://pub.dartlang.org" 387 | source: hosted 388 | version: "2.1.0" 389 | string_scanner: 390 | dependency: transitive 391 | description: 392 | name: string_scanner 393 | url: "https://pub.dartlang.org" 394 | source: hosted 395 | version: "1.1.0" 396 | term_glyph: 397 | dependency: transitive 398 | description: 399 | name: term_glyph 400 | url: "https://pub.dartlang.org" 401 | source: hosted 402 | version: "1.2.0" 403 | test: 404 | dependency: "direct dev" 405 | description: 406 | name: test 407 | url: "https://pub.dartlang.org" 408 | source: hosted 409 | version: "1.16.8" 410 | test_api: 411 | dependency: transitive 412 | description: 413 | name: test_api 414 | url: "https://pub.dartlang.org" 415 | source: hosted 416 | version: "0.3.0" 417 | test_core: 418 | dependency: transitive 419 | description: 420 | name: test_core 421 | url: "https://pub.dartlang.org" 422 | source: hosted 423 | version: "0.3.19" 424 | typed_data: 425 | dependency: transitive 426 | description: 427 | name: typed_data 428 | url: "https://pub.dartlang.org" 429 | source: hosted 430 | version: "1.3.0" 431 | vm_service: 432 | dependency: transitive 433 | description: 434 | name: vm_service 435 | url: "https://pub.dartlang.org" 436 | source: hosted 437 | version: "6.2.0" 438 | watcher: 439 | dependency: transitive 440 | description: 441 | name: watcher 442 | url: "https://pub.dartlang.org" 443 | source: hosted 444 | version: "1.0.0" 445 | web_socket_channel: 446 | dependency: transitive 447 | description: 448 | name: web_socket_channel 449 | url: "https://pub.dartlang.org" 450 | source: hosted 451 | version: "2.0.0" 452 | webkit_inspection_protocol: 453 | dependency: transitive 454 | description: 455 | name: webkit_inspection_protocol 456 | url: "https://pub.dartlang.org" 457 | source: hosted 458 | version: "1.0.0" 459 | yaml: 460 | dependency: transitive 461 | description: 462 | name: yaml 463 | url: "https://pub.dartlang.org" 464 | source: hosted 465 | version: "3.1.0" 466 | sdks: 467 | dart: ">=2.15.0 <3.0.0" 468 | -------------------------------------------------------------------------------- /app/lib/src/generated/contract.pb.dart: -------------------------------------------------------------------------------- 1 | /// 2 | // Generated code. Do not modify. 3 | // source: lib/src/protos/contract.proto 4 | // 5 | // @dart = 2.12 6 | // ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields 7 | 8 | import 'dart:core' as $core; 9 | 10 | import 'package:protobuf/protobuf.dart' as $pb; 11 | 12 | class Empty extends $pb.GeneratedMessage { 13 | static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'Empty', createEmptyInstance: create) 14 | ..hasRequiredFields = false 15 | ; 16 | 17 | Empty._() : super(); 18 | factory Empty() => create(); 19 | factory Empty.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); 20 | factory Empty.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); 21 | @$core.Deprecated( 22 | 'Using this can add significant overhead to your binary. ' 23 | 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' 24 | 'Will be removed in next major version') 25 | Empty clone() => Empty()..mergeFromMessage(this); 26 | @$core.Deprecated( 27 | 'Using this can add significant overhead to your binary. ' 28 | 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' 29 | 'Will be removed in next major version') 30 | Empty copyWith(void Function(Empty) updates) => super.copyWith((message) => updates(message as Empty)) as Empty; // ignore: deprecated_member_use 31 | $pb.BuilderInfo get info_ => _i; 32 | @$core.pragma('dart2js:noInline') 33 | static Empty create() => Empty._(); 34 | Empty createEmptyInstance() => create(); 35 | static $pb.PbList createRepeated() => $pb.PbList(); 36 | @$core.pragma('dart2js:noInline') 37 | static Empty getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); 38 | static Empty? _defaultInstance; 39 | } 40 | 41 | class Hospital extends $pb.GeneratedMessage { 42 | static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'Hospital', createEmptyInstance: create) 43 | ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'name') 44 | ..aOM(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'coordinate', subBuilder: Location.create) 45 | ..aOS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'type') 46 | ..aOS(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'location') 47 | ..hasRequiredFields = false 48 | ; 49 | 50 | Hospital._() : super(); 51 | factory Hospital({ 52 | $core.String? name, 53 | Location? coordinate, 54 | $core.String? type, 55 | $core.String? location, 56 | }) { 57 | final _result = create(); 58 | if (name != null) { 59 | _result.name = name; 60 | } 61 | if (coordinate != null) { 62 | _result.coordinate = coordinate; 63 | } 64 | if (type != null) { 65 | _result.type = type; 66 | } 67 | if (location != null) { 68 | _result.location = location; 69 | } 70 | return _result; 71 | } 72 | factory Hospital.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); 73 | factory Hospital.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); 74 | @$core.Deprecated( 75 | 'Using this can add significant overhead to your binary. ' 76 | 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' 77 | 'Will be removed in next major version') 78 | Hospital clone() => Hospital()..mergeFromMessage(this); 79 | @$core.Deprecated( 80 | 'Using this can add significant overhead to your binary. ' 81 | 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' 82 | 'Will be removed in next major version') 83 | Hospital copyWith(void Function(Hospital) updates) => super.copyWith((message) => updates(message as Hospital)) as Hospital; // ignore: deprecated_member_use 84 | $pb.BuilderInfo get info_ => _i; 85 | @$core.pragma('dart2js:noInline') 86 | static Hospital create() => Hospital._(); 87 | Hospital createEmptyInstance() => create(); 88 | static $pb.PbList createRepeated() => $pb.PbList(); 89 | @$core.pragma('dart2js:noInline') 90 | static Hospital getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); 91 | static Hospital? _defaultInstance; 92 | 93 | @$pb.TagNumber(1) 94 | $core.String get name => $_getSZ(0); 95 | @$pb.TagNumber(1) 96 | set name($core.String v) { $_setString(0, v); } 97 | @$pb.TagNumber(1) 98 | $core.bool hasName() => $_has(0); 99 | @$pb.TagNumber(1) 100 | void clearName() => clearField(1); 101 | 102 | @$pb.TagNumber(2) 103 | Location get coordinate => $_getN(1); 104 | @$pb.TagNumber(2) 105 | set coordinate(Location v) { setField(2, v); } 106 | @$pb.TagNumber(2) 107 | $core.bool hasCoordinate() => $_has(1); 108 | @$pb.TagNumber(2) 109 | void clearCoordinate() => clearField(2); 110 | @$pb.TagNumber(2) 111 | Location ensureCoordinate() => $_ensure(1); 112 | 113 | @$pb.TagNumber(3) 114 | $core.String get type => $_getSZ(2); 115 | @$pb.TagNumber(3) 116 | set type($core.String v) { $_setString(2, v); } 117 | @$pb.TagNumber(3) 118 | $core.bool hasType() => $_has(2); 119 | @$pb.TagNumber(3) 120 | void clearType() => clearField(3); 121 | 122 | @$pb.TagNumber(4) 123 | $core.String get location => $_getSZ(3); 124 | @$pb.TagNumber(4) 125 | set location($core.String v) { $_setString(3, v); } 126 | @$pb.TagNumber(4) 127 | $core.bool hasLocation() => $_has(3); 128 | @$pb.TagNumber(4) 129 | void clearLocation() => clearField(4); 130 | } 131 | 132 | class Hospitals extends $pb.GeneratedMessage { 133 | static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'Hospitals', createEmptyInstance: create) 134 | ..pc(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'hospitals', $pb.PbFieldType.PM, subBuilder: Hospital.create) 135 | ..hasRequiredFields = false 136 | ; 137 | 138 | Hospitals._() : super(); 139 | factory Hospitals({ 140 | $core.Iterable? hospitals, 141 | }) { 142 | final _result = create(); 143 | if (hospitals != null) { 144 | _result.hospitals.addAll(hospitals); 145 | } 146 | return _result; 147 | } 148 | factory Hospitals.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); 149 | factory Hospitals.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); 150 | @$core.Deprecated( 151 | 'Using this can add significant overhead to your binary. ' 152 | 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' 153 | 'Will be removed in next major version') 154 | Hospitals clone() => Hospitals()..mergeFromMessage(this); 155 | @$core.Deprecated( 156 | 'Using this can add significant overhead to your binary. ' 157 | 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' 158 | 'Will be removed in next major version') 159 | Hospitals copyWith(void Function(Hospitals) updates) => super.copyWith((message) => updates(message as Hospitals)) as Hospitals; // ignore: deprecated_member_use 160 | $pb.BuilderInfo get info_ => _i; 161 | @$core.pragma('dart2js:noInline') 162 | static Hospitals create() => Hospitals._(); 163 | Hospitals createEmptyInstance() => create(); 164 | static $pb.PbList createRepeated() => $pb.PbList(); 165 | @$core.pragma('dart2js:noInline') 166 | static Hospitals getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); 167 | static Hospitals? _defaultInstance; 168 | 169 | @$pb.TagNumber(1) 170 | $core.List get hospitals => $_getList(0); 171 | } 172 | 173 | class SearchQuery extends $pb.GeneratedMessage { 174 | static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'SearchQuery', createEmptyInstance: create) 175 | ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'value') 176 | ..hasRequiredFields = false 177 | ; 178 | 179 | SearchQuery._() : super(); 180 | factory SearchQuery({ 181 | $core.String? value, 182 | }) { 183 | final _result = create(); 184 | if (value != null) { 185 | _result.value = value; 186 | } 187 | return _result; 188 | } 189 | factory SearchQuery.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); 190 | factory SearchQuery.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); 191 | @$core.Deprecated( 192 | 'Using this can add significant overhead to your binary. ' 193 | 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' 194 | 'Will be removed in next major version') 195 | SearchQuery clone() => SearchQuery()..mergeFromMessage(this); 196 | @$core.Deprecated( 197 | 'Using this can add significant overhead to your binary. ' 198 | 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' 199 | 'Will be removed in next major version') 200 | SearchQuery copyWith(void Function(SearchQuery) updates) => super.copyWith((message) => updates(message as SearchQuery)) as SearchQuery; // ignore: deprecated_member_use 201 | $pb.BuilderInfo get info_ => _i; 202 | @$core.pragma('dart2js:noInline') 203 | static SearchQuery create() => SearchQuery._(); 204 | SearchQuery createEmptyInstance() => create(); 205 | static $pb.PbList createRepeated() => $pb.PbList(); 206 | @$core.pragma('dart2js:noInline') 207 | static SearchQuery getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); 208 | static SearchQuery? _defaultInstance; 209 | 210 | @$pb.TagNumber(1) 211 | $core.String get value => $_getSZ(0); 212 | @$pb.TagNumber(1) 213 | set value($core.String v) { $_setString(0, v); } 214 | @$pb.TagNumber(1) 215 | $core.bool hasValue() => $_has(0); 216 | @$pb.TagNumber(1) 217 | void clearValue() => clearField(1); 218 | } 219 | 220 | class Location extends $pb.GeneratedMessage { 221 | static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'Location', createEmptyInstance: create) 222 | ..a<$core.double>(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'latitude', $pb.PbFieldType.OD) 223 | ..a<$core.double>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'longitude', $pb.PbFieldType.OD) 224 | ..hasRequiredFields = false 225 | ; 226 | 227 | Location._() : super(); 228 | factory Location({ 229 | $core.double? latitude, 230 | $core.double? longitude, 231 | }) { 232 | final _result = create(); 233 | if (latitude != null) { 234 | _result.latitude = latitude; 235 | } 236 | if (longitude != null) { 237 | _result.longitude = longitude; 238 | } 239 | return _result; 240 | } 241 | factory Location.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); 242 | factory Location.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); 243 | @$core.Deprecated( 244 | 'Using this can add significant overhead to your binary. ' 245 | 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' 246 | 'Will be removed in next major version') 247 | Location clone() => Location()..mergeFromMessage(this); 248 | @$core.Deprecated( 249 | 'Using this can add significant overhead to your binary. ' 250 | 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' 251 | 'Will be removed in next major version') 252 | Location copyWith(void Function(Location) updates) => super.copyWith((message) => updates(message as Location)) as Location; // ignore: deprecated_member_use 253 | $pb.BuilderInfo get info_ => _i; 254 | @$core.pragma('dart2js:noInline') 255 | static Location create() => Location._(); 256 | Location createEmptyInstance() => create(); 257 | static $pb.PbList createRepeated() => $pb.PbList(); 258 | @$core.pragma('dart2js:noInline') 259 | static Location getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); 260 | static Location? _defaultInstance; 261 | 262 | @$pb.TagNumber(1) 263 | $core.double get latitude => $_getN(0); 264 | @$pb.TagNumber(1) 265 | set latitude($core.double v) { $_setDouble(0, v); } 266 | @$pb.TagNumber(1) 267 | $core.bool hasLatitude() => $_has(0); 268 | @$pb.TagNumber(1) 269 | void clearLatitude() => clearField(1); 270 | 271 | @$pb.TagNumber(2) 272 | $core.double get longitude => $_getN(1); 273 | @$pb.TagNumber(2) 274 | set longitude($core.double v) { $_setDouble(1, v); } 275 | @$pb.TagNumber(2) 276 | $core.bool hasLongitude() => $_has(1); 277 | @$pb.TagNumber(2) 278 | void clearLongitude() => clearField(2); 279 | } 280 | 281 | -------------------------------------------------------------------------------- /server/lib/src/protos/generated/contract.pb.dart: -------------------------------------------------------------------------------- 1 | /// 2 | // Generated code. Do not modify. 3 | // source: lib/src/protos/contract.proto 4 | // 5 | // @dart = 2.12 6 | // ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields 7 | 8 | import 'dart:core' as $core; 9 | 10 | import 'package:protobuf/protobuf.dart' as $pb; 11 | 12 | class Empty extends $pb.GeneratedMessage { 13 | static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'Empty', createEmptyInstance: create) 14 | ..hasRequiredFields = false 15 | ; 16 | 17 | Empty._() : super(); 18 | factory Empty() => create(); 19 | factory Empty.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); 20 | factory Empty.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); 21 | @$core.Deprecated( 22 | 'Using this can add significant overhead to your binary. ' 23 | 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' 24 | 'Will be removed in next major version') 25 | Empty clone() => Empty()..mergeFromMessage(this); 26 | @$core.Deprecated( 27 | 'Using this can add significant overhead to your binary. ' 28 | 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' 29 | 'Will be removed in next major version') 30 | Empty copyWith(void Function(Empty) updates) => super.copyWith((message) => updates(message as Empty)) as Empty; // ignore: deprecated_member_use 31 | $pb.BuilderInfo get info_ => _i; 32 | @$core.pragma('dart2js:noInline') 33 | static Empty create() => Empty._(); 34 | Empty createEmptyInstance() => create(); 35 | static $pb.PbList createRepeated() => $pb.PbList(); 36 | @$core.pragma('dart2js:noInline') 37 | static Empty getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); 38 | static Empty? _defaultInstance; 39 | } 40 | 41 | class Hospital extends $pb.GeneratedMessage { 42 | static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'Hospital', createEmptyInstance: create) 43 | ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'name') 44 | ..aOM(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'coordinate', subBuilder: Location.create) 45 | ..aOS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'type') 46 | ..aOS(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'location') 47 | ..hasRequiredFields = false 48 | ; 49 | 50 | Hospital._() : super(); 51 | factory Hospital({ 52 | $core.String? name, 53 | Location? coordinate, 54 | $core.String? type, 55 | $core.String? location, 56 | }) { 57 | final _result = create(); 58 | if (name != null) { 59 | _result.name = name; 60 | } 61 | if (coordinate != null) { 62 | _result.coordinate = coordinate; 63 | } 64 | if (type != null) { 65 | _result.type = type; 66 | } 67 | if (location != null) { 68 | _result.location = location; 69 | } 70 | return _result; 71 | } 72 | factory Hospital.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); 73 | factory Hospital.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); 74 | @$core.Deprecated( 75 | 'Using this can add significant overhead to your binary. ' 76 | 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' 77 | 'Will be removed in next major version') 78 | Hospital clone() => Hospital()..mergeFromMessage(this); 79 | @$core.Deprecated( 80 | 'Using this can add significant overhead to your binary. ' 81 | 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' 82 | 'Will be removed in next major version') 83 | Hospital copyWith(void Function(Hospital) updates) => super.copyWith((message) => updates(message as Hospital)) as Hospital; // ignore: deprecated_member_use 84 | $pb.BuilderInfo get info_ => _i; 85 | @$core.pragma('dart2js:noInline') 86 | static Hospital create() => Hospital._(); 87 | Hospital createEmptyInstance() => create(); 88 | static $pb.PbList createRepeated() => $pb.PbList(); 89 | @$core.pragma('dart2js:noInline') 90 | static Hospital getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); 91 | static Hospital? _defaultInstance; 92 | 93 | @$pb.TagNumber(1) 94 | $core.String get name => $_getSZ(0); 95 | @$pb.TagNumber(1) 96 | set name($core.String v) { $_setString(0, v); } 97 | @$pb.TagNumber(1) 98 | $core.bool hasName() => $_has(0); 99 | @$pb.TagNumber(1) 100 | void clearName() => clearField(1); 101 | 102 | @$pb.TagNumber(2) 103 | Location get coordinate => $_getN(1); 104 | @$pb.TagNumber(2) 105 | set coordinate(Location v) { setField(2, v); } 106 | @$pb.TagNumber(2) 107 | $core.bool hasCoordinate() => $_has(1); 108 | @$pb.TagNumber(2) 109 | void clearCoordinate() => clearField(2); 110 | @$pb.TagNumber(2) 111 | Location ensureCoordinate() => $_ensure(1); 112 | 113 | @$pb.TagNumber(3) 114 | $core.String get type => $_getSZ(2); 115 | @$pb.TagNumber(3) 116 | set type($core.String v) { $_setString(2, v); } 117 | @$pb.TagNumber(3) 118 | $core.bool hasType() => $_has(2); 119 | @$pb.TagNumber(3) 120 | void clearType() => clearField(3); 121 | 122 | @$pb.TagNumber(4) 123 | $core.String get location => $_getSZ(3); 124 | @$pb.TagNumber(4) 125 | set location($core.String v) { $_setString(3, v); } 126 | @$pb.TagNumber(4) 127 | $core.bool hasLocation() => $_has(3); 128 | @$pb.TagNumber(4) 129 | void clearLocation() => clearField(4); 130 | } 131 | 132 | class Hospitals extends $pb.GeneratedMessage { 133 | static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'Hospitals', createEmptyInstance: create) 134 | ..pc(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'hospitals', $pb.PbFieldType.PM, subBuilder: Hospital.create) 135 | ..hasRequiredFields = false 136 | ; 137 | 138 | Hospitals._() : super(); 139 | factory Hospitals({ 140 | $core.Iterable? hospitals, 141 | }) { 142 | final _result = create(); 143 | if (hospitals != null) { 144 | _result.hospitals.addAll(hospitals); 145 | } 146 | return _result; 147 | } 148 | factory Hospitals.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); 149 | factory Hospitals.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); 150 | @$core.Deprecated( 151 | 'Using this can add significant overhead to your binary. ' 152 | 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' 153 | 'Will be removed in next major version') 154 | Hospitals clone() => Hospitals()..mergeFromMessage(this); 155 | @$core.Deprecated( 156 | 'Using this can add significant overhead to your binary. ' 157 | 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' 158 | 'Will be removed in next major version') 159 | Hospitals copyWith(void Function(Hospitals) updates) => super.copyWith((message) => updates(message as Hospitals)) as Hospitals; // ignore: deprecated_member_use 160 | $pb.BuilderInfo get info_ => _i; 161 | @$core.pragma('dart2js:noInline') 162 | static Hospitals create() => Hospitals._(); 163 | Hospitals createEmptyInstance() => create(); 164 | static $pb.PbList createRepeated() => $pb.PbList(); 165 | @$core.pragma('dart2js:noInline') 166 | static Hospitals getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); 167 | static Hospitals? _defaultInstance; 168 | 169 | @$pb.TagNumber(1) 170 | $core.List get hospitals => $_getList(0); 171 | } 172 | 173 | class SearchQuery extends $pb.GeneratedMessage { 174 | static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'SearchQuery', createEmptyInstance: create) 175 | ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'value') 176 | ..hasRequiredFields = false 177 | ; 178 | 179 | SearchQuery._() : super(); 180 | factory SearchQuery({ 181 | $core.String? value, 182 | }) { 183 | final _result = create(); 184 | if (value != null) { 185 | _result.value = value; 186 | } 187 | return _result; 188 | } 189 | factory SearchQuery.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); 190 | factory SearchQuery.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); 191 | @$core.Deprecated( 192 | 'Using this can add significant overhead to your binary. ' 193 | 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' 194 | 'Will be removed in next major version') 195 | SearchQuery clone() => SearchQuery()..mergeFromMessage(this); 196 | @$core.Deprecated( 197 | 'Using this can add significant overhead to your binary. ' 198 | 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' 199 | 'Will be removed in next major version') 200 | SearchQuery copyWith(void Function(SearchQuery) updates) => super.copyWith((message) => updates(message as SearchQuery)) as SearchQuery; // ignore: deprecated_member_use 201 | $pb.BuilderInfo get info_ => _i; 202 | @$core.pragma('dart2js:noInline') 203 | static SearchQuery create() => SearchQuery._(); 204 | SearchQuery createEmptyInstance() => create(); 205 | static $pb.PbList createRepeated() => $pb.PbList(); 206 | @$core.pragma('dart2js:noInline') 207 | static SearchQuery getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); 208 | static SearchQuery? _defaultInstance; 209 | 210 | @$pb.TagNumber(1) 211 | $core.String get value => $_getSZ(0); 212 | @$pb.TagNumber(1) 213 | set value($core.String v) { $_setString(0, v); } 214 | @$pb.TagNumber(1) 215 | $core.bool hasValue() => $_has(0); 216 | @$pb.TagNumber(1) 217 | void clearValue() => clearField(1); 218 | } 219 | 220 | class Location extends $pb.GeneratedMessage { 221 | static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'Location', createEmptyInstance: create) 222 | ..a<$core.double>(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'latitude', $pb.PbFieldType.OD) 223 | ..a<$core.double>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'longitude', $pb.PbFieldType.OD) 224 | ..hasRequiredFields = false 225 | ; 226 | 227 | Location._() : super(); 228 | factory Location({ 229 | $core.double? latitude, 230 | $core.double? longitude, 231 | }) { 232 | final _result = create(); 233 | if (latitude != null) { 234 | _result.latitude = latitude; 235 | } 236 | if (longitude != null) { 237 | _result.longitude = longitude; 238 | } 239 | return _result; 240 | } 241 | factory Location.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); 242 | factory Location.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); 243 | @$core.Deprecated( 244 | 'Using this can add significant overhead to your binary. ' 245 | 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' 246 | 'Will be removed in next major version') 247 | Location clone() => Location()..mergeFromMessage(this); 248 | @$core.Deprecated( 249 | 'Using this can add significant overhead to your binary. ' 250 | 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' 251 | 'Will be removed in next major version') 252 | Location copyWith(void Function(Location) updates) => super.copyWith((message) => updates(message as Location)) as Location; // ignore: deprecated_member_use 253 | $pb.BuilderInfo get info_ => _i; 254 | @$core.pragma('dart2js:noInline') 255 | static Location create() => Location._(); 256 | Location createEmptyInstance() => create(); 257 | static $pb.PbList createRepeated() => $pb.PbList(); 258 | @$core.pragma('dart2js:noInline') 259 | static Location getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); 260 | static Location? _defaultInstance; 261 | 262 | @$pb.TagNumber(1) 263 | $core.double get latitude => $_getN(0); 264 | @$pb.TagNumber(1) 265 | set latitude($core.double v) { $_setDouble(0, v); } 266 | @$pb.TagNumber(1) 267 | $core.bool hasLatitude() => $_has(0); 268 | @$pb.TagNumber(1) 269 | void clearLatitude() => clearField(1); 270 | 271 | @$pb.TagNumber(2) 272 | $core.double get longitude => $_getN(1); 273 | @$pb.TagNumber(2) 274 | set longitude($core.double v) { $_setDouble(1, v); } 275 | @$pb.TagNumber(2) 276 | $core.bool hasLongitude() => $_has(1); 277 | @$pb.TagNumber(2) 278 | void clearLongitude() => clearField(2); 279 | } 280 | 281 | -------------------------------------------------------------------------------- /app/ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 11 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 12 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 13 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 14 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 15 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 16 | /* End PBXBuildFile section */ 17 | 18 | /* Begin PBXCopyFilesBuildPhase section */ 19 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 20 | isa = PBXCopyFilesBuildPhase; 21 | buildActionMask = 2147483647; 22 | dstPath = ""; 23 | dstSubfolderSpec = 10; 24 | files = ( 25 | ); 26 | name = "Embed Frameworks"; 27 | runOnlyForDeploymentPostprocessing = 0; 28 | }; 29 | /* End PBXCopyFilesBuildPhase section */ 30 | 31 | /* Begin PBXFileReference section */ 32 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 33 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 34 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 35 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 36 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 37 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 38 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 39 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 40 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 41 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 42 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 43 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 44 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 45 | /* End PBXFileReference section */ 46 | 47 | /* Begin PBXFrameworksBuildPhase section */ 48 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 49 | isa = PBXFrameworksBuildPhase; 50 | buildActionMask = 2147483647; 51 | files = ( 52 | ); 53 | runOnlyForDeploymentPostprocessing = 0; 54 | }; 55 | /* End PBXFrameworksBuildPhase section */ 56 | 57 | /* Begin PBXGroup section */ 58 | 9740EEB11CF90186004384FC /* Flutter */ = { 59 | isa = PBXGroup; 60 | children = ( 61 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 62 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 63 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 64 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 65 | ); 66 | name = Flutter; 67 | sourceTree = ""; 68 | }; 69 | 97C146E51CF9000F007C117D = { 70 | isa = PBXGroup; 71 | children = ( 72 | 9740EEB11CF90186004384FC /* Flutter */, 73 | 97C146F01CF9000F007C117D /* Runner */, 74 | 97C146EF1CF9000F007C117D /* Products */, 75 | ); 76 | sourceTree = ""; 77 | }; 78 | 97C146EF1CF9000F007C117D /* Products */ = { 79 | isa = PBXGroup; 80 | children = ( 81 | 97C146EE1CF9000F007C117D /* Runner.app */, 82 | ); 83 | name = Products; 84 | sourceTree = ""; 85 | }; 86 | 97C146F01CF9000F007C117D /* Runner */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 90 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 91 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 92 | 97C147021CF9000F007C117D /* Info.plist */, 93 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 94 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 95 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 96 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 97 | ); 98 | path = Runner; 99 | sourceTree = ""; 100 | }; 101 | /* End PBXGroup section */ 102 | 103 | /* Begin PBXNativeTarget section */ 104 | 97C146ED1CF9000F007C117D /* Runner */ = { 105 | isa = PBXNativeTarget; 106 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 107 | buildPhases = ( 108 | 9740EEB61CF901F6004384FC /* Run Script */, 109 | 97C146EA1CF9000F007C117D /* Sources */, 110 | 97C146EB1CF9000F007C117D /* Frameworks */, 111 | 97C146EC1CF9000F007C117D /* Resources */, 112 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 113 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 114 | ); 115 | buildRules = ( 116 | ); 117 | dependencies = ( 118 | ); 119 | name = Runner; 120 | productName = Runner; 121 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 122 | productType = "com.apple.product-type.application"; 123 | }; 124 | /* End PBXNativeTarget section */ 125 | 126 | /* Begin PBXProject section */ 127 | 97C146E61CF9000F007C117D /* Project object */ = { 128 | isa = PBXProject; 129 | attributes = { 130 | LastUpgradeCheck = 1020; 131 | ORGANIZATIONNAME = ""; 132 | TargetAttributes = { 133 | 97C146ED1CF9000F007C117D = { 134 | CreatedOnToolsVersion = 7.3.1; 135 | LastSwiftMigration = 1100; 136 | }; 137 | }; 138 | }; 139 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 140 | compatibilityVersion = "Xcode 9.3"; 141 | developmentRegion = en; 142 | hasScannedForEncodings = 0; 143 | knownRegions = ( 144 | en, 145 | Base, 146 | ); 147 | mainGroup = 97C146E51CF9000F007C117D; 148 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 149 | projectDirPath = ""; 150 | projectRoot = ""; 151 | targets = ( 152 | 97C146ED1CF9000F007C117D /* Runner */, 153 | ); 154 | }; 155 | /* End PBXProject section */ 156 | 157 | /* Begin PBXResourcesBuildPhase section */ 158 | 97C146EC1CF9000F007C117D /* Resources */ = { 159 | isa = PBXResourcesBuildPhase; 160 | buildActionMask = 2147483647; 161 | files = ( 162 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 163 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 164 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 165 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 166 | ); 167 | runOnlyForDeploymentPostprocessing = 0; 168 | }; 169 | /* End PBXResourcesBuildPhase section */ 170 | 171 | /* Begin PBXShellScriptBuildPhase section */ 172 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 173 | isa = PBXShellScriptBuildPhase; 174 | buildActionMask = 2147483647; 175 | files = ( 176 | ); 177 | inputPaths = ( 178 | ); 179 | name = "Thin Binary"; 180 | outputPaths = ( 181 | ); 182 | runOnlyForDeploymentPostprocessing = 0; 183 | shellPath = /bin/sh; 184 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; 185 | }; 186 | 9740EEB61CF901F6004384FC /* Run Script */ = { 187 | isa = PBXShellScriptBuildPhase; 188 | buildActionMask = 2147483647; 189 | files = ( 190 | ); 191 | inputPaths = ( 192 | ); 193 | name = "Run Script"; 194 | outputPaths = ( 195 | ); 196 | runOnlyForDeploymentPostprocessing = 0; 197 | shellPath = /bin/sh; 198 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 199 | }; 200 | /* End PBXShellScriptBuildPhase section */ 201 | 202 | /* Begin PBXSourcesBuildPhase section */ 203 | 97C146EA1CF9000F007C117D /* Sources */ = { 204 | isa = PBXSourcesBuildPhase; 205 | buildActionMask = 2147483647; 206 | files = ( 207 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 208 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 209 | ); 210 | runOnlyForDeploymentPostprocessing = 0; 211 | }; 212 | /* End PBXSourcesBuildPhase section */ 213 | 214 | /* Begin PBXVariantGroup section */ 215 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 216 | isa = PBXVariantGroup; 217 | children = ( 218 | 97C146FB1CF9000F007C117D /* Base */, 219 | ); 220 | name = Main.storyboard; 221 | sourceTree = ""; 222 | }; 223 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 224 | isa = PBXVariantGroup; 225 | children = ( 226 | 97C147001CF9000F007C117D /* Base */, 227 | ); 228 | name = LaunchScreen.storyboard; 229 | sourceTree = ""; 230 | }; 231 | /* End PBXVariantGroup section */ 232 | 233 | /* Begin XCBuildConfiguration section */ 234 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 235 | isa = XCBuildConfiguration; 236 | buildSettings = { 237 | ALWAYS_SEARCH_USER_PATHS = NO; 238 | CLANG_ANALYZER_NONNULL = YES; 239 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 240 | CLANG_CXX_LIBRARY = "libc++"; 241 | CLANG_ENABLE_MODULES = YES; 242 | CLANG_ENABLE_OBJC_ARC = YES; 243 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 244 | CLANG_WARN_BOOL_CONVERSION = YES; 245 | CLANG_WARN_COMMA = YES; 246 | CLANG_WARN_CONSTANT_CONVERSION = YES; 247 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 248 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 249 | CLANG_WARN_EMPTY_BODY = YES; 250 | CLANG_WARN_ENUM_CONVERSION = YES; 251 | CLANG_WARN_INFINITE_RECURSION = YES; 252 | CLANG_WARN_INT_CONVERSION = YES; 253 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 254 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 255 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 256 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 257 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 258 | CLANG_WARN_STRICT_PROTOTYPES = YES; 259 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 260 | CLANG_WARN_UNREACHABLE_CODE = YES; 261 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 262 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 263 | COPY_PHASE_STRIP = NO; 264 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 265 | ENABLE_NS_ASSERTIONS = NO; 266 | ENABLE_STRICT_OBJC_MSGSEND = YES; 267 | GCC_C_LANGUAGE_STANDARD = gnu99; 268 | GCC_NO_COMMON_BLOCKS = YES; 269 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 270 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 271 | GCC_WARN_UNDECLARED_SELECTOR = YES; 272 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 273 | GCC_WARN_UNUSED_FUNCTION = YES; 274 | GCC_WARN_UNUSED_VARIABLE = YES; 275 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 276 | MTL_ENABLE_DEBUG_INFO = NO; 277 | SDKROOT = iphoneos; 278 | SUPPORTED_PLATFORMS = iphoneos; 279 | TARGETED_DEVICE_FAMILY = "1,2"; 280 | VALIDATE_PRODUCT = YES; 281 | }; 282 | name = Profile; 283 | }; 284 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 285 | isa = XCBuildConfiguration; 286 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 287 | buildSettings = { 288 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 289 | CLANG_ENABLE_MODULES = YES; 290 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 291 | ENABLE_BITCODE = NO; 292 | INFOPLIST_FILE = Runner/Info.plist; 293 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 294 | PRODUCT_BUNDLE_IDENTIFIER = com.myhospitalurl.help.hospitalsRiverpod; 295 | PRODUCT_NAME = "$(TARGET_NAME)"; 296 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 297 | SWIFT_VERSION = 5.0; 298 | VERSIONING_SYSTEM = "apple-generic"; 299 | }; 300 | name = Profile; 301 | }; 302 | 97C147031CF9000F007C117D /* Debug */ = { 303 | isa = XCBuildConfiguration; 304 | buildSettings = { 305 | ALWAYS_SEARCH_USER_PATHS = NO; 306 | CLANG_ANALYZER_NONNULL = YES; 307 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 308 | CLANG_CXX_LIBRARY = "libc++"; 309 | CLANG_ENABLE_MODULES = YES; 310 | CLANG_ENABLE_OBJC_ARC = YES; 311 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 312 | CLANG_WARN_BOOL_CONVERSION = YES; 313 | CLANG_WARN_COMMA = YES; 314 | CLANG_WARN_CONSTANT_CONVERSION = YES; 315 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 316 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 317 | CLANG_WARN_EMPTY_BODY = YES; 318 | CLANG_WARN_ENUM_CONVERSION = YES; 319 | CLANG_WARN_INFINITE_RECURSION = YES; 320 | CLANG_WARN_INT_CONVERSION = YES; 321 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 322 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 323 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 324 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 325 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 326 | CLANG_WARN_STRICT_PROTOTYPES = YES; 327 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 328 | CLANG_WARN_UNREACHABLE_CODE = YES; 329 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 330 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 331 | COPY_PHASE_STRIP = NO; 332 | DEBUG_INFORMATION_FORMAT = dwarf; 333 | ENABLE_STRICT_OBJC_MSGSEND = YES; 334 | ENABLE_TESTABILITY = YES; 335 | GCC_C_LANGUAGE_STANDARD = gnu99; 336 | GCC_DYNAMIC_NO_PIC = NO; 337 | GCC_NO_COMMON_BLOCKS = YES; 338 | GCC_OPTIMIZATION_LEVEL = 0; 339 | GCC_PREPROCESSOR_DEFINITIONS = ( 340 | "DEBUG=1", 341 | "$(inherited)", 342 | ); 343 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 344 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 345 | GCC_WARN_UNDECLARED_SELECTOR = YES; 346 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 347 | GCC_WARN_UNUSED_FUNCTION = YES; 348 | GCC_WARN_UNUSED_VARIABLE = YES; 349 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 350 | MTL_ENABLE_DEBUG_INFO = YES; 351 | ONLY_ACTIVE_ARCH = YES; 352 | SDKROOT = iphoneos; 353 | TARGETED_DEVICE_FAMILY = "1,2"; 354 | }; 355 | name = Debug; 356 | }; 357 | 97C147041CF9000F007C117D /* Release */ = { 358 | isa = XCBuildConfiguration; 359 | buildSettings = { 360 | ALWAYS_SEARCH_USER_PATHS = NO; 361 | CLANG_ANALYZER_NONNULL = YES; 362 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 363 | CLANG_CXX_LIBRARY = "libc++"; 364 | CLANG_ENABLE_MODULES = YES; 365 | CLANG_ENABLE_OBJC_ARC = YES; 366 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 367 | CLANG_WARN_BOOL_CONVERSION = YES; 368 | CLANG_WARN_COMMA = YES; 369 | CLANG_WARN_CONSTANT_CONVERSION = YES; 370 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 371 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 372 | CLANG_WARN_EMPTY_BODY = YES; 373 | CLANG_WARN_ENUM_CONVERSION = YES; 374 | CLANG_WARN_INFINITE_RECURSION = YES; 375 | CLANG_WARN_INT_CONVERSION = YES; 376 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 377 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 378 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 379 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 380 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 381 | CLANG_WARN_STRICT_PROTOTYPES = YES; 382 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 383 | CLANG_WARN_UNREACHABLE_CODE = YES; 384 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 385 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 386 | COPY_PHASE_STRIP = NO; 387 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 388 | ENABLE_NS_ASSERTIONS = NO; 389 | ENABLE_STRICT_OBJC_MSGSEND = YES; 390 | GCC_C_LANGUAGE_STANDARD = gnu99; 391 | GCC_NO_COMMON_BLOCKS = YES; 392 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 393 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 394 | GCC_WARN_UNDECLARED_SELECTOR = YES; 395 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 396 | GCC_WARN_UNUSED_FUNCTION = YES; 397 | GCC_WARN_UNUSED_VARIABLE = YES; 398 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 399 | MTL_ENABLE_DEBUG_INFO = NO; 400 | SDKROOT = iphoneos; 401 | SUPPORTED_PLATFORMS = iphoneos; 402 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 403 | TARGETED_DEVICE_FAMILY = "1,2"; 404 | VALIDATE_PRODUCT = YES; 405 | }; 406 | name = Release; 407 | }; 408 | 97C147061CF9000F007C117D /* Debug */ = { 409 | isa = XCBuildConfiguration; 410 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 411 | buildSettings = { 412 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 413 | CLANG_ENABLE_MODULES = YES; 414 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 415 | ENABLE_BITCODE = NO; 416 | INFOPLIST_FILE = Runner/Info.plist; 417 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 418 | PRODUCT_BUNDLE_IDENTIFIER = com.myhospitalurl.help.hospitalsRiverpod; 419 | PRODUCT_NAME = "$(TARGET_NAME)"; 420 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 421 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 422 | SWIFT_VERSION = 5.0; 423 | VERSIONING_SYSTEM = "apple-generic"; 424 | }; 425 | name = Debug; 426 | }; 427 | 97C147071CF9000F007C117D /* Release */ = { 428 | isa = XCBuildConfiguration; 429 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 430 | buildSettings = { 431 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 432 | CLANG_ENABLE_MODULES = YES; 433 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 434 | ENABLE_BITCODE = NO; 435 | INFOPLIST_FILE = Runner/Info.plist; 436 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 437 | PRODUCT_BUNDLE_IDENTIFIER = com.myhospitalurl.help.hospitalsRiverpod; 438 | PRODUCT_NAME = "$(TARGET_NAME)"; 439 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 440 | SWIFT_VERSION = 5.0; 441 | VERSIONING_SYSTEM = "apple-generic"; 442 | }; 443 | name = Release; 444 | }; 445 | /* End XCBuildConfiguration section */ 446 | 447 | /* Begin XCConfigurationList section */ 448 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 449 | isa = XCConfigurationList; 450 | buildConfigurations = ( 451 | 97C147031CF9000F007C117D /* Debug */, 452 | 97C147041CF9000F007C117D /* Release */, 453 | 249021D3217E4FDB00AE95B9 /* Profile */, 454 | ); 455 | defaultConfigurationIsVisible = 0; 456 | defaultConfigurationName = Release; 457 | }; 458 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 459 | isa = XCConfigurationList; 460 | buildConfigurations = ( 461 | 97C147061CF9000F007C117D /* Debug */, 462 | 97C147071CF9000F007C117D /* Release */, 463 | 249021D4217E4FDB00AE95B9 /* Profile */, 464 | ); 465 | defaultConfigurationIsVisible = 0; 466 | defaultConfigurationName = Release; 467 | }; 468 | /* End XCConfigurationList section */ 469 | }; 470 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 471 | } 472 | --------------------------------------------------------------------------------