Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(flutter_todos): Add firestore backend. Support compile time API selection. #3510

Open
wants to merge 23 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
1308eed
Update flutter_todos pubspec to latest stable
maximveksler Aug 16, 2022
4d63de6
Update local_storage pubspec to latest stable
maximveksler Aug 16, 2022
f11b1c9
Update todos_api pubspec to latest stable
maximveksler Aug 16, 2022
6b0f1b3
Update todos_repository pubspec to latest stable
maximveksler Aug 16, 2022
0519e96
meta downgrade dep conflict with flutter_test
maximveksler Aug 17, 2022
72ae4f3
Factory for TodosAPI implementations.
maximveksler Aug 17, 2022
871c871
Cherry pick firestore_todos_api
maximveksler Aug 17, 2022
baad619
Support for FirestoreTodosApi in factory
maximveksler Aug 17, 2022
7f50f29
Updating firebase dependencies
maximveksler Aug 17, 2022
988e5dc
Update to very_good template 0.7.13
maximveksler Aug 17, 2022
f34e8a5
vgv_cli bug where windows was not disabled from template generation
maximveksler Aug 17, 2022
a6de40a
lint on firestore_todos_api
maximveksler Aug 18, 2022
2e1f6af
Set minSdkVersion to 21
maximveksler Aug 18, 2022
6a465d0
Implementaion rational
maximveksler Aug 18, 2022
27bb8de
More WAS
maximveksler Aug 18, 2022
b494110
Use --dart-define for todos_api selection.
maximveksler Aug 18, 2022
9d358e5
Add firebase creds handling documentation.
maximveksler Aug 18, 2022
3a39d82
Merge remote-tracking branch 'origin' into feat/todos-firestore
maximveksler Sep 15, 2022
51b0e54
Merge branch 'felangel:master' into feat/todos-firestore
maximveksler Sep 15, 2022
a1bcfee
Default example is shipped with Firebase disabled
maximveksler Sep 15, 2022
d16a035
Merge branch 'felangel:master' into feat/todos-firestore
maximveksler Nov 5, 2022
f9f5981
Merge branch 'felangel:master' into feat/todos-firestore
maximveksler Nov 8, 2022
dffcc38
Merge branch 'master' into feat/todos-firestore
maximveksler Nov 29, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions examples/flutter_todos/.vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,40 @@
"type": "dart",
"program": "lib/main_production.dart",
"args": ["--flavor", "production", "--target", "lib/main_production.dart"]
},
{
"name": "Launch development (Firebase)",
"request": "launch",
"type": "dart",
"program": "lib/main_development.dart",
"args": [
"--flavor",
"development",
"--target",
"lib/main_development.dart"
],
"toolArgs": ["--dart-define", "todos_api=firestore"]
},
{
"name": "Launch staging (Firebase)",
"request": "launch",
"type": "dart",
"program": "lib/main_staging.dart",
"args": ["--flavor", "staging", "--target", "lib/main_staging.dart"],
"toolArgs": ["--dart-define", "todos_api=firestore"]
},
{
"name": "Launch production (Firebase)",
"request": "launch",
"type": "dart",
"program": "lib/main_production.dart",
"args": [
"--flavor",
"production",
"--target",
"lib/main_production.dart"
],
"toolArgs": ["--dart-define", "todos_api=firestore"]
}
]
}
2 changes: 1 addition & 1 deletion examples/flutter_todos/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ android {
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.example.verygoodcore.flutter_todos"
minSdkVersion flutter.minSdkVersion
minSdkVersion 21 // cloud_firestore >= 19, Auto MultiDex >=21
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
Expand Down
118 changes: 118 additions & 0 deletions examples/flutter_todos/docs/firebase/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# Flutter Todos Firebase

## Getting Started 🚀

This project contains 3 flavors:

- development
- staging
- production

## Firestore - Setup 😍

Using Firebase Firestore as the backend requires per-project setup.

### Install

```
firebase login
dart pub global activate flutterfire_cli
```

For more information follow the steps at https://firebase.google.com/docs/flutter/setup

### Configure

Based on the chosen flavor apply once the `flutterfire configure` command to integrate firebase into your project.

```sh
# Development
$ flutterfire configure -a 'com.example.verygoodcore.flutter_todos.dev' -i 'com.example.verygoodcore.flutter-todos.dev'

# Staging
$ flutterfire configure -a 'com.example.verygoodcore.flutter_todos.stg' -i 'com.example.verygoodcore.flutter-todos.stg'

# Production
$ flutterfire configure -a 'com.example.verygoodcore.flutter_todos' -i 'com.example.verygoodcore.flutter-todos'
```

## Getting Started 🚀

To run the desired flavor either use the launch configuration in VSCode/Android Studio or use the following commands:

```sh
# Development
$ flutter run --flavor development --target lib/main_development.dart --dart-define=todos_api=firestore

# Staging
$ flutter run --flavor staging --target lib/main_staging.dart --dart-define=todos_api=firestore

# Production
$ flutter run --flavor production --target lib/main_production.dart --dart-define=todos_api=firestore
```

_\*Flutter Todos works on iOS, Android, and Web._

---

## FAQ

Q: What does `flutterfire configure` changes in my project?

A: The command registers app bundles and fetchs credentails from Firebase API required to communicate with the service.
The credentials are writen locally into the project directories to be shipped with the app.

**VALID to commit changes**

```bash
$ git status
Changes not staged for commit:
modified: examples/flutter_todos/android/app/build.gradle
modified: examples/flutter_todos/android/build.gradle
```

```git
$ git diff
diff --git a/examples/flutter_todos/android/app/build.gradle b/examples/flutter_todos/android/app/build.gradle
--- a/examples/flutter_todos/android/app/build.gradle
+++ b/examples/flutter_todos/android/app/build.gradle
@@ -28,6 +28,9 @@ if (keystorePropertiesFile.exists()) {
}

apply plugin: 'com.android.application'
+// START: FlutterFire Configuration
+apply plugin: 'com.google.gms.google-services'
+// END: FlutterFire Configuration
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"

diff --git a/examples/flutter_todos/android/build.gradle b/examples/flutter_todos/android/build.gradle
index ba6fe4a4..4f69aab2 100644
--- a/examples/flutter_todos/android/build.gradle
+++ b/examples/flutter_todos/android/build.gradle
@@ -7,6 +7,9 @@ buildscript {

dependencies {
classpath 'com.android.tools.build:gradle:7.1.2'
+ // START: FlutterFire Configuration
+ classpath 'com.google.gms:google-services:4.3.10'
+ // END: FlutterFire Configuration
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
```

**DO NOT Commit these changes**

⚠️ Note: For both security and flavored app considerations credential **should not** be committed into git repo.

```bash
$ git status
Untracked files:
examples/flutter_todos/android/app/google-services.json
examples/flutter_todos/ios/Runner/GoogleService-Info.plist
examples/flutter_todos/ios/firebase_app_id_file.json
examples/flutter_todos/lib/firebase_options.dart
```

_Use CI to integrate them into the artifact at runtime during the build phase of the app._
1 change: 0 additions & 1 deletion examples/flutter_todos/ios/Flutter/Debug.xcconfig
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"
1 change: 0 additions & 1 deletion examples/flutter_todos/ios/Flutter/Release.xcconfig
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 6 additions & 5 deletions examples/flutter_todos/lib/main_development.dart
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import 'package:flutter/widgets.dart';
import 'package:flutter_todos/bootstrap.dart';
import 'package:local_storage_todos_api/local_storage_todos_api.dart';
import 'package:todos_api_factory/todos_api_factory.dart';

Future<void> main() async {
Future<void> main(List<String> args) async {
WidgetsFlutterBinding.ensureInitialized();

final todosApi = LocalStorageTodosApi(
plugin: await SharedPreferences.getInstance(),
);
const implementation =
String.fromEnvironment('todos_api', defaultValue: 'localStorage');

final todosApi = await TodosApiFactory.factory(implementation);

bootstrap(todosApi: todosApi);
}
11 changes: 6 additions & 5 deletions examples/flutter_todos/lib/main_production.dart
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import 'package:flutter/widgets.dart';
import 'package:flutter_todos/bootstrap.dart';
import 'package:local_storage_todos_api/local_storage_todos_api.dart';
import 'package:todos_api_factory/todos_api_factory.dart';

Future<void> main() async {
Future<void> main(List<String> args) async {
WidgetsFlutterBinding.ensureInitialized();

final todosApi = LocalStorageTodosApi(
plugin: await SharedPreferences.getInstance(),
);
const implementation =
String.fromEnvironment('todos_api', defaultValue: 'localStorage');

final todosApi = await TodosApiFactory.factory(implementation);

bootstrap(todosApi: todosApi);
}
11 changes: 6 additions & 5 deletions examples/flutter_todos/lib/main_staging.dart
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import 'package:flutter/widgets.dart';
import 'package:flutter_todos/bootstrap.dart';
import 'package:local_storage_todos_api/local_storage_todos_api.dart';
import 'package:todos_api_factory/todos_api_factory.dart';

Future<void> main() async {
Future<void> main(List<String> args) async {
WidgetsFlutterBinding.ensureInitialized();

final todosApi = LocalStorageTodosApi(
plugin: await SharedPreferences.getInstance(),
);
const implementation =
String.fromEnvironment('todos_api', defaultValue: 'localStorage');

final todosApi = await TodosApiFactory.factory(implementation);

bootstrap(todosApi: todosApi);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# See https://www.dartlang.org/guides/libraries/private-files

# Files and directories created by pub
.dart_tool/
.packages
build/
pubspec.lock
11 changes: 11 additions & 0 deletions examples/flutter_todos/packages/firestore_todos_api/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# firestore_todos_api

[![style: very good analysis][very_good_analysis_badge]][very_good_analysis_link]
[![License: MIT][license_badge]][license_link]

My new Firestore package

[license_badge]: https://img.shields.io/badge/license-MIT-blue.svg
[license_link]: https://opensource.org/licenses/MIT
[very_good_analysis_badge]: https://img.shields.io/badge/style-very_good_analysis-B22C89.svg
[very_good_analysis_link]: https://pub.dev/packages/very_good_analysis
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include: package:very_good_analysis/analysis_options.2.4.0.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
library firestore_todos_api;

export 'src/firestore_todos_api.dart';
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:todos_api/todos_api.dart';

/// {@template firestore_todos_api}
/// Firestore implementation for the Todos example
/// {@endtemplate}
class FirestoreTodosApi implements TodosApi {
/// {@macro firestore_todos_api}
FirestoreTodosApi({
required FirebaseFirestore firestore,
}) : _firestore = firestore;

final FirebaseFirestore _firestore;

/// a converter method for maintaining type-safety
late final todosCollection =
_firestore.collection('todos').withConverter<Todo>(
fromFirestore: (snapshot, _) => Todo.fromJson(snapshot.data()!),
toFirestore: (todo, _) => todo.toJson(),
);

/// This stream orders the [Todo]'s by the
/// time they were created, and then converts
/// them from a [DocumentSnapshot] into
/// a [Todo]
@override
Stream<List<Todo>> getTodos() {
return todosCollection.orderBy('id').snapshots().map(
(snapshot) => snapshot.docs.map((e) => e.data()).toList(),
);
}

/// This method first checks whether or not a todo exists
/// If it doesn't, then we add a timestamp to the todo in
/// order to preserve the order they were added
/// Else, we update the existing one
@override
Future<void> saveTodo(Todo todo) async {
final check = await todosCollection.where('id', isEqualTo: todo.id).get();

if (check.docs.isEmpty) {
await todosCollection.add(todo);
} else {
final currentTodoId = check.docs[0].reference.id;
await todosCollection.doc(currentTodoId).update(todo.toJson());
}
}

/// This method first checks to see if the todo
/// exists, and if so it deletes it
// TODO(fix): check out dismissiable bug

@override
Future<void> deleteTodo(String id) async {
final check = await todosCollection.where('id', isEqualTo: id).get();

if (check.docs.isEmpty) {
throw TodoNotFoundException();
} else {
final currentTodoId = check.docs[0].reference.id;
await todosCollection.doc(currentTodoId).delete();
}
}

/// This method uses the Batch write api for
/// executing multiple operations in a single call,
/// which in this case is to delete all the todos that
/// are marked completed
@override
Future<int> clearCompleted() {
final batch = _firestore.batch();
return todosCollection
.where('isCompleted', isEqualTo: true)
.get()
.then((querySnapshot) {
final completedTodosAmount = querySnapshot.docs.length;
for (final document in querySnapshot.docs) {
batch.delete(document.reference);
}
batch.commit();
return completedTodosAmount;
});
}

/// This method uses the Batch write api for
/// executing multiple operations in a single call,
/// which in this case is to mark all the todos as
/// completed
@override
Future<int> completeAll({required bool isCompleted}) {
final batch = _firestore.batch();
return todosCollection.get().then((querySnapshot) {
final completedTodosAmount = querySnapshot.docs.length;
for (final document in querySnapshot.docs) {
final completedTodo = document.data().copyWith(isCompleted: true);
batch.update(document.reference, completedTodo.toJson());
}
batch.commit();
return completedTodosAmount;
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: firestore_todos_api
description: My new Firestore package
version: 1.0.0+1
publish_to: none

environment:
sdk: ">=2.17.0 <3.0.0"

dependencies:
cloud_firestore: ^3.2.0
fake_cloud_firestore: ^1.2.4
flutter:
sdk: flutter
todos_api:
path: ../todos_api

dev_dependencies:
coverage: ^1.3.2
mocktail: ^0.3.0
test: ^1.21.1
very_good_analysis: ^3.0.1
Loading