HILLEL COREN BUILDING APPS WITH FLUTTERREDUX SAMPLE Always use Built Value Avoid using a...

46
BUILDING APPS WITH FLUTTER HILLEL COREN

Transcript of HILLEL COREN BUILDING APPS WITH FLUTTERREDUX SAMPLE Always use Built Value Avoid using a...

Page 1: HILLEL COREN BUILDING APPS WITH FLUTTERREDUX SAMPLE Always use Built Value Avoid using a StoreBuilder in main.dart Pass view model to the view/avoid mapping Restructure to group by

BUILDING APPS WITH FLUTTERHILLEL COREN

Page 2: HILLEL COREN BUILDING APPS WITH FLUTTERREDUX SAMPLE Always use Built Value Avoid using a StoreBuilder in main.dart Pass view model to the view/avoid mapping Restructure to group by

A BIT ABOUT ME…

�2

Page 3: HILLEL COREN BUILDING APPS WITH FLUTTERREDUX SAMPLE Always use Built Value Avoid using a StoreBuilder in main.dart Pass view model to the view/avoid mapping Restructure to group by

APP STORE RATING

�3

Page 4: HILLEL COREN BUILDING APPS WITH FLUTTERREDUX SAMPLE Always use Built Value Avoid using a StoreBuilder in main.dart Pass view model to the view/avoid mapping Restructure to group by

“FLUTTER IS AWESOME!”

–Most developers who have tried it

�4

Page 5: HILLEL COREN BUILDING APPS WITH FLUTTERREDUX SAMPLE Always use Built Value Avoid using a StoreBuilder in main.dart Pass view model to the view/avoid mapping Restructure to group by
Page 6: HILLEL COREN BUILDING APPS WITH FLUTTERREDUX SAMPLE Always use Built Value Avoid using a StoreBuilder in main.dart Pass view model to the view/avoid mapping Restructure to group by

STATE MANAGEMENT

▸ setState

▸ Provider

▸ BLoC

▸ MobX

▸ Redux

�6

Page 7: HILLEL COREN BUILDING APPS WITH FLUTTERREDUX SAMPLE Always use Built Value Avoid using a StoreBuilder in main.dart Pass view model to the view/avoid mapping Restructure to group by

*https://blog.novoda.com/introduction-to-redux-in-flutter/

�7

Page 8: HILLEL COREN BUILDING APPS WITH FLUTTERREDUX SAMPLE Always use Built Value Avoid using a StoreBuilder in main.dart Pass view model to the view/avoid mapping Restructure to group by

REDUX PERFORMANCE

▸ Rebuilding BLoC vs Redux

▸ Importance of memoization

▸ Set distinct on the view model

▸ Keep number of layers to a minimum

�8

Page 9: HILLEL COREN BUILDING APPS WITH FLUTTERREDUX SAMPLE Always use Built Value Avoid using a StoreBuilder in main.dart Pass view model to the view/avoid mapping Restructure to group by

REDUX SAMPLE

▸ Always use Built Value

▸ Avoid using a StoreBuilder in main.dart

▸ Pass view model to the view/avoid mapping

▸ Restructure to group by feature

▸ Only need flutter_redux and redux_logging

�9

Page 10: HILLEL COREN BUILDING APPS WITH FLUTTERREDUX SAMPLE Always use Built Value Avoid using a StoreBuilder in main.dart Pass view model to the view/avoid mapping Restructure to group by

�10

Page 11: HILLEL COREN BUILDING APPS WITH FLUTTERREDUX SAMPLE Always use Built Value Avoid using a StoreBuilder in main.dart Pass view model to the view/avoid mapping Restructure to group by

�11

Page 12: HILLEL COREN BUILDING APPS WITH FLUTTERREDUX SAMPLE Always use Built Value Avoid using a StoreBuilder in main.dart Pass view model to the view/avoid mapping Restructure to group by

PERSISTENCE

▸ Separate AppState in to data, ui and auth

▸ Persist each section separately

▸ Use PersistUI and PersistData interfaces

▸ Use similar approach to track loading

▸ Clear state if app version is different

�12

Page 13: HILLEL COREN BUILDING APPS WITH FLUTTERREDUX SAMPLE Always use Built Value Avoid using a StoreBuilder in main.dart Pass view model to the view/avoid mapping Restructure to group by

abstract class AppState

implements Built<AppState, AppStateBuilder> {

bool get isLoading;

bool get isSaving;

AuthState get authState;

DataState get dataState;

UIState get uiState;

}

�13

Page 14: HILLEL COREN BUILDING APPS WITH FLUTTERREDUX SAMPLE Always use Built Value Avoid using a StoreBuilder in main.dart Pass view model to the view/avoid mapping Restructure to group by

// App actions

class StartLoading {}

class StopLoading {}

class PersistUI {}

class PersistData {}

// Client actions

class LoadClientRequest implements StartLoading {}

class LoadClientSuccess implements StopLoading,

PersistData {

final BuiltList<ClientEntity> clients;

LoadClientSuccess(this.clients);

}

�14

Page 15: HILLEL COREN BUILDING APPS WITH FLUTTERREDUX SAMPLE Always use Built Value Avoid using a StoreBuilder in main.dart Pass view model to the view/avoid mapping Restructure to group by

// App actions

class StartLoading {}

class StopLoading {}

class PersistUI {}

class PersistData {}

// Client actions

class LoadClientRequest implements StartLoading {}

class LoadClientSuccess implements StopLoading,

PersistData {

final BuiltList<ClientEntity> clients;

LoadClientSuccess(this.clients);

}

�15

Page 16: HILLEL COREN BUILDING APPS WITH FLUTTERREDUX SAMPLE Always use Built Value Avoid using a StoreBuilder in main.dart Pass view model to the view/avoid mapping Restructure to group by

// App actions

class StartLoading {}

class StopLoading {}

class PersistUI {}

class PersistData {}

// Client actions

class LoadClientRequest implements StartLoading {}

class LoadClientSuccess implements StopLoading,

PersistData {

final BuiltList<ClientEntity> clients;

LoadClientSuccess(this.clients);

}

�16

Page 17: HILLEL COREN BUILDING APPS WITH FLUTTERREDUX SAMPLE Always use Built Value Avoid using a StoreBuilder in main.dart Pass view model to the view/avoid mapping Restructure to group by

EntityUIState productReducer(ProductState state, dynamic action) {

return state.rebuild((b) => b

..editing = editingReducer(state.editing, action).toBuilder());

}

EntityUIState productReducer(ProductState state, dynamic action) {

return state.rebuild((b) => b

..editing.replace(editingReducer(state.editing, action)));

}

Error: A value of type ‘…’ can't be assigned to a variable of type ‘…Builder’.

Page 18: HILLEL COREN BUILDING APPS WITH FLUTTERREDUX SAMPLE Always use Built Value Avoid using a StoreBuilder in main.dart Pass view model to the view/avoid mapping Restructure to group by

EntityUIState productReducer(ProductState state, dynamic action) {

return state.rebuild((b) => b

..editing = editingReducer(state.editing, action).toBuilder());

}

EntityUIState productReducer(ProductState state, dynamic action) {

return state.rebuild((b) => b

..editing.replace(editingReducer(state.editing, action)));

}

Error: A value of type ‘…’ can't be assigned to a variable of type ‘…Builder’.

Page 19: HILLEL COREN BUILDING APPS WITH FLUTTERREDUX SAMPLE Always use Built Value Avoid using a StoreBuilder in main.dart Pass view model to the view/avoid mapping Restructure to group by

DATA CACHING

▸ All data is cached to the device

▸ Last updated timestamp is stored in the state

▸ Pass the timestamp when requesting new data

▸ Pull to refresh or auto-refresh after 15 minutes

▸ Autoload after certain events. ie, saving an invoice

�19

Page 20: HILLEL COREN BUILDING APPS WITH FLUTTERREDUX SAMPLE Always use Built Value Avoid using a StoreBuilder in main.dart Pass view model to the view/avoid mapping Restructure to group by

abstract class ProductState

implements Built<ProductState, ProductStateBuilder> {

int get lastUpdated;

BuiltMap<int, ProductEntity> get map;

bool get isLoaded => lastUpdated > 0;

bool get isStale {

if (! isLoaded) {

return true;

}

return DateTime.now().millisecondsSinceEpoch

- lastUpdated > kMillisecondsToRefreshData;

}

}

�20

Page 21: HILLEL COREN BUILDING APPS WITH FLUTTERREDUX SAMPLE Always use Built Value Avoid using a StoreBuilder in main.dart Pass view model to the view/avoid mapping Restructure to group by

abstract class ProductState

implements Built<ProductState, ProductStateBuilder> {

int get lastUpdated;

BuiltMap<int, ProductEntity> get map;

bool get isLoaded => lastUpdated > 0;

bool get isStale {

if (! isLoaded) {

return true;

}

return DateTime.now().millisecondsSinceEpoch

- lastUpdated > kMillisecondsToRefreshData;

}

}

�21

Page 22: HILLEL COREN BUILDING APPS WITH FLUTTERREDUX SAMPLE Always use Built Value Avoid using a StoreBuilder in main.dart Pass view model to the view/avoid mapping Restructure to group by

abstract class ProductState

implements Built<ProductState, ProductStateBuilder> {

int get lastUpdated;

BuiltMap<int, ProductEntity> get map;

bool get isLoaded => lastUpdated > 0;

bool get isStale {

if (! isLoaded) {

return true;

}

return DateTime.now().millisecondsSinceEpoch

- lastUpdated > kMillisecondsToRefreshData;

}

}

�22

Page 23: HILLEL COREN BUILDING APPS WITH FLUTTERREDUX SAMPLE Always use Built Value Avoid using a StoreBuilder in main.dart Pass view model to the view/avoid mapping Restructure to group by

VIEW MODELS

▸ View code should be UI/layout focused

▸ Nested view models

▸ Options to pass data to views

▸ Pass AppState as property on view model

▸ Pass field as property on view model

▸ Access AppState in view using context

�23

Page 24: HILLEL COREN BUILDING APPS WITH FLUTTERREDUX SAMPLE Always use Built Value Avoid using a StoreBuilder in main.dart Pass view model to the view/avoid mapping Restructure to group by

MEMOIZATION

▸ Keep build method fast

▸ Simple Dart package available: memoize

▸ memoX where X is the number of parameters

�24

Page 25: HILLEL COREN BUILDING APPS WITH FLUTTERREDUX SAMPLE Always use Built Value Avoid using a StoreBuilder in main.dart Pass view model to the view/avoid mapping Restructure to group by

List<int> activeClientsSelector(

BuiltMap<int, ClientEntity> clientMap,

BuiltList<int> clientList) {

return clientList.where((clientId) =>

clientMap[clientId].isActive).toList();

}

final memoizedActiveClients = memo2((

BuiltMap<int, ClientEntity> clientMap,

BuiltList<int> clientList) =>

activeClientsSelector(clientMap, clientList));

Page 26: HILLEL COREN BUILDING APPS WITH FLUTTERREDUX SAMPLE Always use Built Value Avoid using a StoreBuilder in main.dart Pass view model to the view/avoid mapping Restructure to group by

List<int> activeClientsSelector(

BuiltMap<int, ClientEntity> clientMap,

BuiltList<int> clientList) {

return clientList.where((clientId) =>

clientMap[clientId].isActive).toList();

}

final memoizedActiveClients = memo2((

BuiltMap<int, ClientEntity> clientMap,

BuiltList<int> clientList) =>

activeClientsSelector(clientMap, clientList));

Page 27: HILLEL COREN BUILDING APPS WITH FLUTTERREDUX SAMPLE Always use Built Value Avoid using a StoreBuilder in main.dart Pass view model to the view/avoid mapping Restructure to group by

FORMS

▸ Built Value enables change tracking

▸ Prefer didChangeDependencies() over iniState()

▸ Avoid @nullable, set default value in constructor

▸ Static negative counter for new ids

▸ Use completers in view models

�27

Page 28: HILLEL COREN BUILDING APPS WITH FLUTTERREDUX SAMPLE Always use Built Value Avoid using a StoreBuilder in main.dart Pass view model to the view/avoid mapping Restructure to group by

void _onChanged() {

final oldProduct = widget.viewModel.product;

final newProduct = oldProduct.rebuild((b) => b

..notes = _notesController.text.trim());

if (newProduct != oldProduct) {

widget.viewModel.onChanged(newProduct);

}

}

�28

Page 29: HILLEL COREN BUILDING APPS WITH FLUTTERREDUX SAMPLE Always use Built Value Avoid using a StoreBuilder in main.dart Pass view model to the view/avoid mapping Restructure to group by

void _onChanged() {

final oldProduct = widget.viewModel.product;

final newProduct = oldProduct.rebuild((b) => b

..notes = _notesController.text.trim());

if (newProduct != oldProduct) {

widget.viewModel.onChanged(newProduct);

}

}

�29

Page 30: HILLEL COREN BUILDING APPS WITH FLUTTERREDUX SAMPLE Always use Built Value Avoid using a StoreBuilder in main.dart Pass view model to the view/avoid mapping Restructure to group by

void _onChanged() {

final oldProduct = widget.viewModel.product;

final newProduct = oldProduct.rebuild((b) => b

..notes = _notesController.text.trim());

if (newProduct != oldProduct) {

widget.viewModel.onChanged(newProduct);

}

}

�30

Page 31: HILLEL COREN BUILDING APPS WITH FLUTTERREDUX SAMPLE Always use Built Value Avoid using a StoreBuilder in main.dart Pass view model to the view/avoid mapping Restructure to group by

NAVIGATION

▸ Handle navigation in middleware

▸ Pass context in actions

▸ Use completers for wrap up code

▸ Use pop() to pass back value

▸ snackbarCompleter and popCompleter

�31

Page 32: HILLEL COREN BUILDING APPS WITH FLUTTERREDUX SAMPLE Always use Built Value Avoid using a StoreBuilder in main.dart Pass view model to the view/avoid mapping Restructure to group by

Completer<Null> snackBarCompleter(BuildContext context, String message) {

final Completer<Null> completer = Completer<Null>();

completer.future.then((_) {

Scaffold.of(context)

.showSnackBar(SnackBar(content: SnackBarRow(message: message)));

}).catchError((Object error) {

showDialog<ErrorDialog>(

context: context,

builder: (BuildContext context) => ErrorDialog(error));

});

return completer;

}

Page 33: HILLEL COREN BUILDING APPS WITH FLUTTERREDUX SAMPLE Always use Built Value Avoid using a StoreBuilder in main.dart Pass view model to the view/avoid mapping Restructure to group by

Completer<Null> snackBarCompleter(BuildContext context, String message) {

final Completer<Null> completer = Completer<Null>();

completer.future.then((_) {

Scaffold.of(context)

.showSnackBar(SnackBar(content: SnackBarRow(message: message)));

}).catchError((Object error) {

showDialog<ErrorDialog>(

context: context,

builder: (BuildContext context) => ErrorDialog(error));

});

return completer;

}

Page 34: HILLEL COREN BUILDING APPS WITH FLUTTERREDUX SAMPLE Always use Built Value Avoid using a StoreBuilder in main.dart Pass view model to the view/avoid mapping Restructure to group by

Completer<Null> snackBarCompleter(BuildContext context, String message) {

final Completer<Null> completer = Completer<Null>();

completer.future.then((_) {

Scaffold.of(context)

.showSnackBar(SnackBar(content: SnackBarRow(message: message)));

}).catchError((Object error) {

showDialog<ErrorDialog>(

context: context,

builder: (BuildContext context) => ErrorDialog(error));

});

return completer;

}

Page 35: HILLEL COREN BUILDING APPS WITH FLUTTERREDUX SAMPLE Always use Built Value Avoid using a StoreBuilder in main.dart Pass view model to the view/avoid mapping Restructure to group by

switch (action) {

case EntityAction.archive:

store.dispatch(ArchiveInvoiceRequest(

snackBarCompleter(context, localization.archivedInvoice),

invoice.id));

break;

...

}

Page 36: HILLEL COREN BUILDING APPS WITH FLUTTERREDUX SAMPLE Always use Built Value Avoid using a StoreBuilder in main.dart Pass view model to the view/avoid mapping Restructure to group by

CODE GENERATOR

▸ Reduces need for a lot of copy/replace

▸ Two main commands:

▸ Init: Update code with your app info

▸ Make: Generate individual module

▸ Creates a large amount of code

▸ Important to get it right at the beginning

�36

Page 37: HILLEL COREN BUILDING APPS WITH FLUTTERREDUX SAMPLE Always use Built Value Avoid using a StoreBuilder in main.dart Pass view model to the view/avoid mapping Restructure to group by

PACKAGES

▸ http

▸ path_provider

▸ shared_preferences

▸ font_awesome_flutter

▸ built_value/built_collection

▸ memoize

▸ url_launcher

�37

▸ flutter_slidable

▸ charts_flutter

▸ google_sign_in

▸ firebase_auth

▸ local_auth

▸ share

▸ cached_network_image

Page 38: HILLEL COREN BUILDING APPS WITH FLUTTERREDUX SAMPLE Always use Built Value Avoid using a StoreBuilder in main.dart Pass view model to the view/avoid mapping Restructure to group by

BEST PRACTICES

▸ analysis_options.yaml

▸ sentry.io or Crashlytics

▸ Built Value

▸ Create lots of widgets, ie. IconText

▸ Read the flutter_redux code

�38

Page 39: HILLEL COREN BUILDING APPS WITH FLUTTERREDUX SAMPLE Always use Built Value Avoid using a StoreBuilder in main.dart Pass view model to the view/avoid mapping Restructure to group by

APP STORE RATING

�39

Page 40: HILLEL COREN BUILDING APPS WITH FLUTTERREDUX SAMPLE Always use Built Value Avoid using a StoreBuilder in main.dart Pass view model to the view/avoid mapping Restructure to group by

APP STORE RATING

�40

Page 41: HILLEL COREN BUILDING APPS WITH FLUTTERREDUX SAMPLE Always use Built Value Avoid using a StoreBuilder in main.dart Pass view model to the view/avoid mapping Restructure to group by

PLATFORMS: CURRENT

�41

Mobile

Page 42: HILLEL COREN BUILDING APPS WITH FLUTTERREDUX SAMPLE Always use Built Value Avoid using a StoreBuilder in main.dart Pass view model to the view/avoid mapping Restructure to group by

PLATFORMS: NEXT

�42

Mobile

Desktop

Page 43: HILLEL COREN BUILDING APPS WITH FLUTTERREDUX SAMPLE Always use Built Value Avoid using a StoreBuilder in main.dart Pass view model to the view/avoid mapping Restructure to group by

PLATFORMS: FUTURE

�43

Mobile

Desktop

Web

Page 44: HILLEL COREN BUILDING APPS WITH FLUTTERREDUX SAMPLE Always use Built Value Avoid using a StoreBuilder in main.dart Pass view model to the view/avoid mapping Restructure to group by

WHO TO FOLLOW

▸ Tim Sneath - @timsneath

▸ Eric Seidel - @_eseidel

▸ Martin Aguinis - @MartinAguinis

▸ Nilay Yener - @nlycskn

▸ Andrew Brogdon - @RedBrogdon

▸ Emily Fortuna - @bouncingsheep

▸ Filip Hráček - @filiphracek

�44

Page 45: HILLEL COREN BUILDING APPS WITH FLUTTERREDUX SAMPLE Always use Built Value Avoid using a StoreBuilder in main.dart Pass view model to the view/avoid mapping Restructure to group by

IT’S ALL WIDGETS!

�45

Page 46: HILLEL COREN BUILDING APPS WITH FLUTTERREDUX SAMPLE Always use Built Value Avoid using a StoreBuilder in main.dart Pass view model to the view/avoid mapping Restructure to group by

HILLEL.DEV@HILLELCOREN