Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

What is Firestore

Slide 4

Slide 4 text

What is Firestore “flexible, scalable NoSQL cloud database to store and sync data for client- and server-side development”

Slide 5

Slide 5 text

Channels Documents Group of documents Collections “general” “random” “jobs” Like a JSON file Data can be accessed like a map

Slide 6

Slide 6 text

Paths /users /channels Collection of Users Collection of Channels /users/miguel /channels/general Document ‘miguel’ Document ‘general’

Slide 7

Slide 7 text

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”

Slide 8

Slide 8 text

Subcollections /channels/general/messages/123 Message ‘123’ in channel ‘general’

Slide 9

Slide 9 text

What is better? /channels/general/messages/123 Message ‘123’ in channel ‘general’ /messages/123 Message ‘123’ in top level It depends!

Slide 10

Slide 10 text

Access data Read once a single document Read once a collection of documents Read updates from a single document Read updates from a collection

Slide 11

Slide 11 text

Sample app

Slide 12

Slide 12 text

Sample app “Gif Review” app Submit ratings of GIFs Calculates average ratings

Slide 13

Slide 13 text

/gifs Collection of GIF /gifs/{gifId}/ratings Subcollection of Ratings /gifs/{gifId} GIF document /gifs/{gifId}/ratings/miguel My Rating

Slide 14

Slide 14 text

No content

Slide 15

Slide 15 text

No content

Slide 16

Slide 16 text

No content

Slide 17

Slide 17 text

https://github.com/miquelbeltran/flutter- london-firestore Link on Twitter: @MiBLT

Slide 18

Slide 18 text

Coding basics

Slide 19

Slide 19 text

Getting started Firestore firestore = Firestore.instance

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

Reading documents // Future document = await firestore.document(path).get() // read the field ‘rating’ rating = document[‘rating’] // check if document exists if (!document.exists) { … }

Slide 23

Slide 23 text

Example: Read rating from gif Load value

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

Example: Set rating on tap Tap

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

Create new document // Future await firestore.collection(path).add( { 'rating': rating } )

Slide 28

Slide 28 text

Organising paths class FirestorePaths { static String pathGifs = '/gifs'; static String pathRatings = '/ratings'; static String getPathRating(String id, String user) { return "$pathGifs/$id$pathRatings/$user"; } }

Slide 29

Slide 29 text

Reading collections // Future documents = await firestore .collection(‘/gifs’) .getDocuments()

Slide 30

Slide 30 text

Reading collections documents = await firestore.collection(path) .getDocuments() gifs = documents.documents.map((doc) { // from DocumentSnapshot to Gif return fromDoc(doc) })

Slide 31

Slide 31 text

Map with async Future.wait(documents.documents.map((doc) async { rating = await _getScore(doc.documentID) return fromDoc(doc, rating) }))

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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()

Slide 34

Slide 34 text

Subscribing to data updates

Slide 35

Slide 35 text

Example: Subscribing to rating updates Updated live!

Slide 36

Slide 36 text

Obtaining snapshots // Stream firestore.collection(path).snapshots()

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

Map with async in Future firestore .collection(path) .snapshots() .asyncMap((snap) async { … async call })

Slide 39

Slide 39 text

Real world lessons

Slide 40

Slide 40 text

Organize Firestore files Use Repositories gif_repository.dart rating_repository.dart

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

Reliable data // avoid using int or double double.parse(doc[‘rating']) // safe parsing double.tryParse(doc['rating']) ?? 0.0

Slide 43

Slide 43 text

Defensive Programming Practice defensive programming Recover from database errors Examples: - Filter bad data - Parse with tryParse methods - Default values

Slide 44

Slide 44 text

Unit Testing Can I test this? firestore.document(path).get() Mock “firestore”!! Mock “DocumentReference”!! Mock “DocumentSnapshot”!!

Slide 45

Slide 45 text

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) })

Slide 46

Slide 46 text

Mock methods with Future repository = MockGifRepository() when(repository.getGifs()) .thenAnswer((_) => SynchronousFuture([gif1, gif2]));

Slide 47

Slide 47 text

Mock methods with Stream controller = StreamController>(sync: true) when(ratingsRepository.getRatings("id")) .thenAnswer((_) => controller.stream) controller.add(rating);

Slide 48

Slide 48 text

Build times iOS builds take long time! Source: Martin Jeret @martinjeret
 (codemagic.io)

Slide 49

Slide 49 text

Firestore and Redux

Slide 50

Slide 50 text

Redux Middleware List> createMiddleware( GifRepository gifRepository, ) { return [ TypedMiddleware(_loadGifs(gifRepository)), ]; }

Slide 51

Slide 51 text

Redux Middleware void Function( Store store, LoadGifsAction action, NextDispatcher next, ) _loadGifs(GifRepository gifRepository) { return (store, action, next) async { next(action); final gifs = await gifRepository.getGifs(); store.dispatch(OnLoadedGifsAction(gifs)); }; }

Slide 52

Slide 52 text

Reading a single document myRating = await ratingRepo.getMyRating(id, ‘miguel') store.dispatch(OnLoadedMyRatingAction(myRating))

Slide 53

Slide 53 text

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; }

Slide 54

Slide 54 text

Streams in Redux // in my GifReview Widget @override void dispose() { super.dispose(); store.dispatch(UnsubscribeAction()); } // in the Middleware, on UnsubscribeAction store.state.subscription.cancel();

Slide 55

Slide 55 text

Streams in BLOC Class MyBlock extends BlocBase { dispose() { ratingsStream.close(); } }

Slide 56

Slide 56 text

Streams with StreamBuilder Widget StreamBuilder( stream: ratingsStream Builder: (context, snapshot) { ... } )

Slide 57

Slide 57 text

Quick recap

Slide 58

Slide 58 text

• 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

Slide 59

Slide 59 text

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