Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Flutter X Firestore

Flutter X Firestore

Introduction to Firestore in Flutter with some tips!


Miguel Beltran

July 15, 2019


  1. Flutter ╳ Firestore M i g u e l B

    e l t r a n @ M i B LT b e l t r a n . w o r k
  2. Why this talk • Because Firestore is cool • Building

    (a real) project with Flutter and Firestore • No documentation on how to use Firestore with Flutter • Share my tips and tricks
  3. What is Firestore

  4. What is Firestore “flexible, scalable NoSQL cloud database to store

    and sync data for client- and server-side development”
  5. Channels Documents Group of documents Collections “general” “random” “jobs” Like

    a JSON file Data can be accessed like a map
  6. Paths /users /channels Collection of Users Collection of Channels /users/miguel

    /channels/general Document ‘miguel’ Document ‘general’
  7. Subcollections /channels Collection of Channels /channels/general/messages Subcollection of Messages Messages

    “hello!” “I have a q…” Channels “general” “random” “jobs” Messages “I am hiring” “New job”
  8. Subcollections /channels/general/messages/123 Message ‘123’ in channel ‘general’

  9. What is better? /channels/general/messages/123 Message ‘123’ in channel ‘general’ /messages/123

    Message ‘123’ in top level It depends!
  10. Access data Read once a single document Read once a

    collection of documents Read updates from a single document Read updates from a collection
  11. Sample app

  12. Sample app “Gif Review” app Submit ratings of GIFs Calculates

    average ratings
  13. /gifs Collection of GIF /gifs/{gifId}/ratings Subcollection of Ratings /gifs/{gifId} GIF

    document /gifs/{gifId}/ratings/miguel My Rating
  14. None
  15. None
  16. None
  17. https://github.com/miquelbeltran/flutter- london-firestore Link on Twitter: @MiBLT

  18. Coding basics

  19. Getting started Firestore firestore = Firestore.instance

  20. Firestore methods firestore.collection(path) => CollectionReference firestore.document(path) => DocumentReference

  21. Reading documents // Future<DocumentSnapshot> document = await firestore.document(path).get() // Can

    be Server, Cache or both get({Source source = Source.serverAndCache})
  22. Reading documents // Future<DocumentSnapshot> document = await firestore.document(path).get() // read

    the field ‘rating’ rating = document[‘rating’] // check if document exists if (!document.exists) { … }
  23. Example: Read rating from gif Load value

  24. Example: Read rating of a gif path = FirestorePaths.getPathRating(id, user)

    document = await firestore.document(path).get() if (!document.exists) { return 0.0 } return double.tryParse(document['rating']) ?? 0.0
  25. Example: Set rating on tap Tap

  26. Set data // Future<void> await firestore.document(path).setData( { 'rating': rating }

  27. Create new document // Future<void> await firestore.collection(path).add( { 'rating': rating

    } )
  28. Organising paths class FirestorePaths { static String pathGifs = '/gifs';

    static String pathRatings = '/ratings'; static String getPathRating(String id, String user) { return "$pathGifs/$id$pathRatings/$user"; } }
  29. Reading collections // Future<QuerySnapshot> documents = await firestore .collection(‘/gifs’) .getDocuments()

  30. Reading collections documents = await firestore.collection(path) .getDocuments() gifs = documents.documents.map((doc)

    { // from DocumentSnapshot to Gif return fromDoc(doc) })
  31. Map with async Future.wait(documents.documents.map((doc) async { rating = await _getScore(doc.documentID)

    return fromDoc(doc, rating) }))
  32. Parsing documents Gif fromDoc(DocumentSnapshot doc) { return Gif( doc.documentID, doc['url'],

    ); }
  33. Reading documents & collections Read document with document(path).get() Write document

    with document(path).setData({data}) collection(path).add({data}) Read a collection with collection(path).getDocuments()
  34. Subscribing to data updates

  35. Example: Subscribing to rating updates Updated live!

  36. Obtaining snapshots // Stream<QuerySnapshots> firestore.collection(path).snapshots()

  37. Obtaining snapshots // Stream<QuerySnapshots> firestore.collection(path).snapshots().map((snap) { return snap.documents.map((doc) => fromDoc(doc))

  38. Map with async in Future firestore .collection(path) .snapshots() .asyncMap((snap) async

    { … async call })
  39. Real world lessons

  40. Organize Firestore files Use Repositories gif_repository.dart rating_repository.dart

  41. Organize Firestore files Use Repositories gif_repository.dart rating_repository.dart Do not call

    to Firestore from your Middleware/BLOC/etc. • Testing Firestore is hard • You want this to be easy to replace
  42. Reliable data // avoid using int or double double.parse(doc[‘rating']) //

    safe parsing double.tryParse(doc['rating']) ?? 0.0
  43. Defensive Programming Practice defensive programming Recover from database errors Examples:

    - Filter bad data - Parse with tryParse methods - Default values
  44. Unit Testing Can I test this? firestore.document(path).get() Mock “firestore”!! Mock

    “DocumentReference”!! Mock “DocumentSnapshot”!!
  45. Test “map” methods class MockDocumentSnapshot extends Mock implements DocumentSnapshot {}

    test("should map DocumentSnapshot to Gif", () { final document = MockDocumentSnapshot() when(document["url"]).thenReturn("URL") when(document.documentID).thenReturn(“ID") final outGif = GifRepository.fromDoc(document) expect(outGif, myGif) })
  46. Mock methods with Future repository = MockGifRepository() when(repository.getGifs()) .thenAnswer((_) =>

    SynchronousFuture([gif1, gif2]));
  47. Mock methods with Stream controller = StreamController<List<Rating>>(sync: true) when(ratingsRepository.getRatings("id")) .thenAnswer((_)

    => controller.stream) controller.add(rating);
  48. Build times iOS builds take long time! Source: Martin Jeret

  49. Firestore and Redux

  50. Redux Middleware List<Middleware<AppState>> createMiddleware( GifRepository gifRepository, ) { return [

    TypedMiddleware<AppState, LoadGifsAction>(_loadGifs(gifRepository)), ]; }
  51. Redux Middleware void Function( Store<AppState> store, LoadGifsAction action, NextDispatcher next,

    ) _loadGifs(GifRepository gifRepository) { return (store, action, next) async { next(action); final gifs = await gifRepository.getGifs(); store.dispatch(OnLoadedGifsAction(gifs)); }; }
  52. Reading a single document myRating = await ratingRepo.getMyRating(id, ‘miguel') store.dispatch(OnLoadedMyRatingAction(myRating))

  53. Streams in Redux // In Middleware subscription = ratingRepo.getRatings(id).listen((data) {

    store.dispatch(OnUpdatedRatingsAction(data)); }); store.dispatch(OnSubscribedToRatingsAction(subscription)) Class AppState { // Keep the current subscription in the AppState StreamSubscription subscription; }
  54. Streams in Redux // in my GifReview Widget @override void

    dispose() { super.dispose(); store.dispatch(UnsubscribeAction()); } // in the Middleware, on UnsubscribeAction store.state.subscription.cancel();
  55. Streams in BLOC Class MyBlock extends BlocBase { dispose() {

    ratingsStream.close(); } }
  56. Streams with StreamBuilder Widget StreamBuilder( stream: ratingsStream Builder: (context, snapshot)

    { ... } )
  57. Quick recap

  58. • Documents & collections • Paths • Read data once

    as Future • Read data continuously as Stream • Set data into document • Organize Firestore code into repositories • Unit test map methods & mock repositories • When using Redux, mind Streams
  59. 59 M i g u e l B e l

    t r a n F r e e l a n c e S o f t w a r e D e v e l o p e r C o n s u l t a n t Thank You! @ M i B LT b e l t r a n . w o r k