├── 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 |
--------------------------------------------------------------------------------