is there any difference in these two class implementation? - firebase

I am a beginner of programming and dart lang. I have a question about how to write class about database service related to flutter and firestore.
// what is the difference this
class DbService {
final Firestore _db;
DbService() : _db = Firestore.instance;
Future<QuerySnapshot> getDataCollection(String id) {
return _db.collection(id).getDocuments();
}
}
// and this
class DbService {
final Firestore _db = Firestore.instance;
Future<QuerySnapshot> getDataCollection(String id) {
return _db.collection(id).getDocuments();
}
}
// when use this class
_dbService = DbService();
What is the best practice. or should I use singleton to instantiate this class? Any comments or help is appreciate.

They are the same for computer, but they give me different impression.
2nd one is obvious, where 1st one telling me the constructor used to take value, or may be take value in future version.

Related

Unity async/await and IO operations with Firestore (Firebase)

I've developed a Unity App that uses Firebase as a BaaS and Firestore as a Database.
Firebase has a Client SDK to make calls that are usually called from client to server by an URL endpoint.
My concern is how my methods should be implemented to correctly work on client without blocking the user experience, cause if I made a heavy request, my Unity App is blocked, and no interaction is allowed to the user.
This is the code of my client DatabaseManager with the methods to retrieve a User from Firestore:
public class DatabaseManager
{
public DatabaseManager(FirebaseFirestore db)
{
this.db = db;
}
public async Task<User> GetUserByUIDAsync(string uid)
{
string documentID = uid;
return await AsyncGetDocumentFromCollection<User, User_FirestoreData>(COL_ID_USERS, documentID);
}
public async Task<PlainData> AsyncGetDocumentFromCollection<PlainData, FirestoreData>(string collectionID, string documentID) where PlainData : IConvertToFirestore<FirestoreData> where FirestoreData : IConvertToPlainData<PlainData>
{
try
{
DocumentReference docRef = db.Collection(collectionID).Document(documentID);
DocumentSnapshot documentSnapshot = await docRef.GetSnapshotAsync();
if (documentSnapshot.Exists)
{
Debug.Log("Get Document data for document:" + documentSnapshot.Id);
FirestoreData firestoreData = documentSnapshot.ConvertTo<FirestoreData>();
return firestoreData.ToPlainData();
}
else
{
Debug.Log($"Document {documentSnapshot.Id} does not exist!");
}
}
catch (Exception e)
{
Debug.Log(e);
}
return default(PlainData);
}
}
This is a simple call and when it's called from any MonoBehaviouryou couldn't notice the load difference when you call it like:
using UnityEngine.UI;
public class MyMono : MonoBehaviour
{
private void DatabaseManager db;
[SerializedField] private Button button = null;
private void Awake()
{
button.onClick.AddListener(async ()=> await CustomAwakeAsync(db));
}
private async Task CustomAwakeAsync(DatabaseManager db)
{
//if this Async method is heavier, this will block the main UI thread when the button is pressed
await db.GetUserByUIDAsync("xdfipñfajrfiñar");
}
}
But if instead of GetUserByUIDAsync I make a heavy call, or multiple recursive calls my application UI will freeze until it's finished...which is bad.
How should I build my code to avoid these case?
Note:
My easy way to test if it's blocking UI thread is having this class attached to a GameObject with Image component:
using UnityEngine;
public class InfiniteRotate : MonoBehaviour
{
public float speed = 1;
// Update is called once per frame
private void Update()
{
this.gameObject.transform.Rotate(0, 0, 1 * Time.deltaTime * speed);
}
}
If the image stop spinning, means that async/await is blocking the UI thread.
Your code as shown:
private void CustomAwake(DatabaseManager db)
{
await db.GetUserByUIDAsync("xdfipñfajrfiñar");
}
...should be producing the following error:
error CS4033: The 'await' operator can only be used within an async method. Consider marking this method with the 'async' modifier and changing its return type to 'Task'.
Even if somehow you managed to silence this error the method signature private void CustomAwake(DatabaseManager db) clearly indicates that this method is synchronous to the caller.
OP:
But if instead of GetUserByUIDAsync I make a heavy call, my application UI will freeze until it's finished...which is bad.
So if you are calling this from the same thread as Update, FixedUpdate etc (which by the looks of it you are) then you are going to block Unity and thus slow down your game.
If you are going to use async/await then you need to do so all the way back to the original caller.
Make it asynchronous
Change the method to:
private async Task<User> CustomAwake(DatabaseManager db) // Note the async Task
{
return await db.GetUserByUIDAsync("xdfipñfajrfiñar");
}
...and ensure that whatever calls it uses await in order to get the User.

Flutter - How to register abstract classes in get_it (service locator)

I'm currently working on a simple project crypto_wallet. State management (BLoC) and Value Equality (freezed) creating CRUD operation of DB (Firebase) and in the watch method, I use StreamSubcription code is :
#injectable
class CoinWatcherBloc extends Bloc<CoinWatcherEvent, CoinWatcherState> {
final ICoinRepository _repository;
CoinWatcherBloc(this._repository, this._coinStreamSubscription) :
super(CoinWatcherState.initial());
StreamSubscription<Either<CoinFailure, KtList<CoinEntity>>>? _coinStreamSubscription;
#override
Stream<CoinWatcherState> mapEventToState(CoinWatcherEvent event) async* {
yield* event.map(
watchCoin: (e) async* {
yield CoinWatcherState.loadInProgress();
await _coinStreamSubscription?.cancel();
_coinStreamSubscription = _repository.watchCoin().listen(
(failureOrSuccess) => add(
CoinWatcherEvent.coinsReceived(failureOrSuccess),
),
);
},
coinsReceived: (e) async* {
yield e.failureOrCoin.fold(
(f) => CoinWatcherState.loadFailure(f),
(coin) => CoinWatcherState.loadSuccess(coin),
);
},
);
}
#override
Future<void> close() async {
await _coinStreamSubscription?.cancel();
return super.close();
}
}
And at last I closed the stream. I inject all third party modules on #lazySingleton :
#module
abstract class FirebaseInjectableModule {
#lazySingleton
FirebaseAuth get firebaseAuth => FirebaseAuth.instance;
#lazySingleton
FirebaseFirestore get firebaseFirestore => FirebaseFirestore.instance;
#lazySingleton
GoogleSignIn get googleSignIn => GoogleSignIn();
}
Then It says :
Object/factory with type StreamSubscription<Either<CoinFailure, KtList<CoinEntity>>> is not
registered inside GetIt.
(Did you accidentally do GetIt sl=GetIt.instance(); instead of GetIt sl=GetIt.instance;
Did you forget to register it?)
If I also register this class like this.
#lazySingleton
StreamSubcription get streamSubcription => StreamSubcription();
Then it throws compile time error that abstract classes can't be instantiated like the all Third Party Classes I've registered. How to inject abstract classes? Is there any other way to do this? or I shouldn't use StreamSubcription something else? I'd be thankful <3 :)
You should not add this._coinStreamSubscription to your bloc's constructor. Remove it from the constructor, and declare it as a late final instance.
class CoinWatcherBloc extends Bloc<CoinWatcherEvent, CoinWatcherState> {
final ICoinRepository _repository;
CoinWatcherBloc(this._repository) :
super(CoinWatcherState.initial());
late final StreamSubscription<Either<CoinFailure, KtList<CoinEntity>>>? _coinStreamSubscription;
get_it was trying to inject the abstract streamSubscription since it is in your constructor. And you do not need this. Also, if you look at it from a testing perspective, there is no need to mock the streamSubscription, you can instead mock the class that supplies data to it - which is the repository in this case
I generally just inject the implementation of the abstract class like
#LazySingleton(as:AbstractClass).

Dart/Flutter: How to refer to static variables on an abstract class?

I am writing a Firestore Model abstract class that handles common operations. Each model matches a collection in Firestore... I want to refer to that both from a create instance and before the instance exists. But I can't make collectionPath static because I can't override static methods, variables, getters, etc... I get that.
Could create an instance as needed maybe Model().collectionPath but I couldn't get that to work.
Is there a way to do this? How are others making these types of Models?
This is what I'm trying to do:
abstract class Model {
String get collectionPath => "";
void create() async {
CollectionReference collection = FirebaseFirestore.instance.collection(this.collectionPath);
this.reference = await collection.add(this.toMap());
}
static Stream<QuerySnapshot> snapshots() {
return FirebaseFirestore.instance.collection(this.collectionPath).snapshots();
}
//...
class User extends Model {
String get collectionPath => "users";
//...
I want to be able add an existing instance to the firestore:
User user = User("Values");
user.create();
And I'd also like to load all the users before I've created any particular one:
Widget _buildBody(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: User.snapshots(),

Flutter use Stream or Future?

I'm using Firestore database to store a list of objects. To retrieve them I use the Stream provided by the Firestore package, like this:
class FirestoreApi implements Api {
FirestoreApi._();
static final instance = FirestoreApi._();
#override
Stream<List<Job>> getJobList() {
final path = "users/myUserId/jobs";
final reference = Firestore.instance.collection(path);
final snapshots = reference.snapshots();
return snapshots.map((snapshot) => snapshot.documents.map(
(snapshot) => Job(
id: snapshot.data['uid'],
name: snapshot.data['name']
),
).toList());
}
}
It implements an abstract class:
abstract class Api {
Stream<List<Job>> getJobList();
}
In my Repository class I call it like this:
class Repository {
final FirestoreApi _firestoreApi = FirestoreApi.instance;
Stream<List<job>> getJobList() => _firestoreApi.getJobList();
}
Then in my BloC I call the Repository:
class JobBloc {
final _repository = new Repository();
Stream<List<Job>> getJobList() {
try {
return _repository.getJobList();
} catch (e) {
rethrow;
} finally {}
}
}
And finally here is how I use it in my Widget:
Widget _buildBody(BuildContext context) {
final JobBloc _jobBloc = Provider.of<JobBloc>(context);
return StreamBuilder<List<Job>>(
stream: _jobBloc.getJobList(),
builder: (BuildContext context, AsyncSnapshot<List<Job>> snapshot) {
if (snapshot.hasData) {
return RefreshIndicator(
child: JobList(snapshot.data),
onRefresh: () => _jobBloc.refreshJobList(),
);
} else {
if(snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
} else {
return Center(child: Text("No data"));
}
}
},
);
}
Until here everything works great and my Widget gets updated in real time when something is changed in the Firestore database.
But now I want to go one step further. Lets say that maybe in the future I need to change my api implementation and use a REST api instead of Firestore. I want that my code is prepared for that.
In that case, all the getJobList() methods should return a Future<List<Job>> since the API will not return a Stream (I don't know if that's possible).
I would have another API class like this that now returns Future<List<Job>>:
class RestApi implements Api {
RestApi._();
static final instance = RestApi._();
#override
Future<List<Job>> getJobList() {
//TODO: my rest api implementation
}
}
So the API abstract class would be modified like this:
abstract class Api {
Future<List<Job>> getJobList();
}
Here the updated Repository:
class Repository {
final RestApi _restApi = RestApi.instance;
Future<List<job>> getJobList() => _restApi.getJobList();
}
And finally in my BloC I would sink the list returned by the API in a StreamController like this:
class JobBloc {
final StreamController _jobController = StreamController<List<Job>>.broadcast();
// retrieve data from stream
Stream<List<Job>> get jobList => _jobController.stream;
Future<List<Job>> getJobList() async {
try {
_jobController.sink.add(await _repository.getJobList());
} catch (e) {
rethrow;
} finally {}
}
}
Now the question: I really like that Firestore returns a Stream, it makes my app to be updated in real time. But on the other hand, I would like that my architecture is consistent.
Since I cannot make my REST api to return a Stream, I think the only way possible would be converting the Firebase Stream to a Future but then I would loose the real-time update feature.
Something like this:
class FirestoreApi implements Api {
FirestoreApi._();
static final instance = FirestoreApi._();
#override
Future<List<Job>> getJobList() async {
final path = "users/myUserId/jobs";
final reference = Firestore.instance.collection(path);
final snapshots = reference.snapshots();
Stream<List<Job>> jobs = snapshots.map((snapshot) => snapshot.documents.map(
(snapshot) => Job(
id: snapshot.data['uid'],
name: snapshot.data['name'],
),
).toList());
List<Job> future = await jobs.first;
return future;
}
}
Until now what I've researched is that using the Future will return only one response, so I will lose the real-time functionality.
I would like to know if loosing the real-time feature would be worthy just to make the architecture consistent or if there is a better approach.
Thanks in advance, any ideas or suggestion will be appreciated.
EDIT: Thanks a lot for your comments, I really appreciate them. I actually don't know which one should be marked as accepted answer since all of them have helped me a lot so I decided to give a positive vote to all of you. If anyone doesn't agree with that or this is not the right behaviour in Stackoverflow please let me know
First of all, in my opinion, firebase is not designed to back up a mature project. In the end, you'll end up with a REST api backing up your app. It's true that, you might also end up using both but for different purposes. So i think you should think about firebase as a tool for MVP/proof of concept. I know that Firebase is cool and works well, etc. but the costs are not feasible for a final product.
Now, nobody says that you can't have a REST client implementation that will return a Stream. Check out this Stream.fromFuture(theFuture). You can think of the REST api like a stream that emits only one event (Rx equivalent: Single)
I would also advise to be careful with the real time update feature provided by Firebase, if you transition to a full REST api, you won't be able to do a real time update because REST doesn't work like that. Firebase is using Sockets for communication (if I remember correctly).
I recommended use the Future way, if you take a break and compare the two codes, with the Future way you need to write more, but the architecture is more clean, strong and scalable. In my experience, that's the right way to do good things. Great work
You can also include both methods in the api / repository, and either retrieve a Future or listen to the Stream in the bloc depending on what you want to do. I don't think you need to worry about violating the consistency of REST by also having a method that returns a stream. There is no better way to tap into the real-time functionality of Firestore than to use a stream like you described.
But to just return a Future, you don't have to go through a stream, you can just await a CollectionReference's getDocuments(), something like this:
class FirestoreApi implements Api {
FirestoreApi._();
static final instance = FirestoreApi._();
CollectionReference jobsReference = Firestore.instance.collection("users/myUserId/jobs");
#override
Future<List<Job>> getJobList() async {
QuerySnapshot query = await jobsReference.getDocuments();
List<Job> jobs = query.documents.map((document) => Job(
id: document.data['uid'],
name: document.data['name'],
)).toList();
return jobs;
}
}
It all depends on your app I think. If real time update is an important feature that effects user experience a lot, stick with the Firebase data streams. If real time updates are not a must, you can get data once using Futures. An alternative to Firebase for real time data updates could be GraphQL subscriptions. I would recommend you to check out Hasura for quick implementation of GraphQL API.
It's a good question.
Firestore vs REST API will result in different APIs (Stream vs Future).
Making the code generic won't work here. As you said:
Stream-based APIs will be realtime
Future-based APIs will not
Even the UX would be different.
In the Stream version, you don't need a refresh indicator.
In the Future version, you can reload the data with pull-to-refresh.
I would not recommend to future-proof your code in this case.
If Firestore works well for you, use Streams in all your APIs.
Only if/when you decide to move to a REST API, then you can convert all your APIs (and UX) to use Futures.
Giving up realtime capabilities upfront doesn't seem worth it.

Flutter Sqflite multiple table models

I've gone through many tutorials and samples on how to implement Sqflite in Flutter. Every other example is done using only one model or database table. As defined in these tutorials:
https://pub.dartlang.org/packages/sqflite
https://www.developerlibs.com/2018/07/flutter-sqlite-database-example.html
http://camposha.info/flutter/sqflite-insert-select-show
As far as I've understood, we need to create as many models and helpers as there are tables. For each database table there will be a model.dart file and a helper.dart file.
My question here, is there any way I can have only one helper for all models?
UPDATE
In helper.dart file there is a future "insert". How can I use the same insert future for all models?
Future<Todo> insert(Todo todo) async {
todo.id = await db.insert(tableTodo, todo.toMap());
return todo;
}
I made a few comments advising the contrary, but I just remembered in my last project I did something like that, it was for Firebase Database, but it would quite similar for sqflite.
I created a BaseItem abstract class, with a key, and all models would descend from that one.
I also created a BaseProvider abstract class, which would require a BaseItem and would define the simple methods to access the model.
The upsert and delete methods lay in FirebaseBaseProvider, which extends BaseProvider.
I'll paste parts of it here (removing quite a lot to make it more understandable):
abstract class BaseItem {
const BaseItem({this.key});
final String key;
}
abstract class BaseProvider<T extends BaseItem> {
Future<List<T>> find();
Future<BaseKey> upsert(T item);
Future<int> delete(T item);
}
abstract class FirebaseBaseProvider<T extends BaseItem> {
// Abstract methods which need to be implemented
T fromMap(BaseKey key, dynamic map);
Map<String, dynamic> toJson(BaseKey key, T item);
Future<DatabaseReference> getReference(BaseKey base) async { ... }
BaseKey compileKey(T item, {String useKey}) { ... }
Future<List<T>> find() async {
List<T> result = new List();
// my implementation doesnt work like this,
// as it's firebase based, but this would
// be the place in a Sqflite implementation to use
// fromMap and load the items
return result;
}
Future<BaseKey> upsert(T item) async {
if (item == null) return null;
BaseKey key = compileKey(item);
(await getReference(key)).set(toJson(key, item));
return key;
}
Future<int> delete(T item) async {
if (item == null) return null;
if (item.key != null && item.key != "") {
(await getReference(compileKey(item))).remove();
}
return 0;
}
}
Then, in order to implement a News model, or any other model, then I would create it by simply defining its contents, like that:
class News extends BaseItem {
News({String key, this.creation, this.messageSubject, this.messageBody}) : super(key: key);
final DateTime creation;
final String messageSubject;
final String messageBody;
bool operator ==(o) => o is News && (o.key == key);
int get hashCode => key.hashCode;
}
And it would require its specific provider, that would implement only the toJson and fromMap methods, like that:
class NewsProvider extends FirebaseBaseProvider<News> {
#override
Map<String, dynamic> toJson(BaseKey key, News news) {
return {
"creation": news.creation,
"messageSubject": news.messageSubject,
"messageBody": news.messageBody,
};
}
#override
News fromMap(BaseKey key, dynamic map) {
DateTime creation = map["creation"] == null ? null : DateTime.tryParse(map["creation"] as String);
return new News(
key: key.child.key,
creation: creation,
messageSubject: map["messageSubject"] as String,
messageBody: map["messageBody"] as String,
);
}
}
In the end, NewProvider is providing the find, upsert and delete methods, but their implementation lay on the abstract class, just one implementation of them for all the models, as you wanted.
Of course, my implementation is quite more complicated than that, both because Firebase requires a different approach to obtain/load the items and also as the find method ends up having to be different in each model specific provider. But yet, quite a lot can be simplified.
What I was saying in the comment is that this last class, the specific NewsProvider which have both toJson and fromMap specific implementations may also be generalized by using annotations in the News model class, but that brings quite a lot of problems and obscurity and - in my opinion, of course - it's not worth it.
I had a similar question about creating tables, all the examples just create one table. I found this webpage that created two tables putting an await on each command.
Like this:
Future _onCreate(Database db, int version) async {
await db.execute("CREATE TABLE table1 (id INTEGER, valuex TEXT)");
await db.execute("CREATE TABLE table2 (id INTEGER, valuey TEXT)");
await db.execute("CREATE TABLE table3 (id INTEGER, valuez TEXT)");
}

Resources