Slide 1

Slide 1 text

࣮ྫͱྺ࢙͔ΒֶͿʂ Flutterͷঢ়ଶ؅ཧํ๏ͷબఆ FlutterKaigi 2021 11/29 19:45ʙ entaku / @entaku_0818

Slide 2

Slide 2 text

• Name: ԕ౻୓໻ (entaku) • Twitter: @entaku_0818 • Job: ϞόΠϧΞϓϦΤϯδχΞ • Android / Flutter / iOSͷܦݧ͋Γ • iOSΤϯδχΞͷܦݧ͕௕͍ ࣗݾ঺հ

Slide 3

Slide 3 text

Flutterྺ 2019/4ϦϦʔε 2021/4ϦϦʔε(ϦϓϨΠε)

Slide 4

Slide 4 text

2019/4ϦϦʔε 2021/4ϦϦʔε(ϦϓϨΠε) StatefulWidget Provider Flutterྺ

Slide 5

Slide 5 text

Flutterͷঢ়ଶ؅ཧԿ࢖͑͹͍͍͔Θ͔Βͳ͍ 😥

Slide 6

Slide 6 text

iOSͷ࣌ࣗ෼͸ͲͷΑ͏ʹֶΜ͚ͩͬʁ 🤔

Slide 7

Slide 7 text

https://peaks.cc/books/iOS_architecture

Slide 8

Slide 8 text

https://peaks.cc/books/iOS_architecture ͳͥͦͷઃܭ͕࠾༻͞Ε͍ͯΔͷ͔ʁ Λ࣮ྫͱྺ࢙ͷ؍఺͔Βॻ͔Ε͍ͯΔ

Slide 9

Slide 9 text

ࠓճ๻΋͜ͷΑ͏ͳࢿྉΛ࡞Γ͍ͨʂ 🔥

Slide 10

Slide 10 text

Flutterঢ়ଶ؅ཧͷྺ࢙ͷ೉͠͞ •Flutter ϦϦʔε౰ॳ(2018/12)͔Β3೥ऑ͔͠ܦͬͯͳ͍ •ެࣜͰఏࣔ͞Ε͍ͯΔख๏͸͋Δ΋ͷͷ؅ཧख๏͸ͨ͘͞Μ͋Δ •10ݸҎ্ͷํ๏͕ఏࣔ͞Ε͍ͯΔ https://docs. fl utter.dev/development/data-and-backend/state-mgmt

Slide 11

Slide 11 text

Flutterঢ়ଶ؅ཧͷͨ͘͞Μͷํ๏ StatefulWidget Provider Riverpod InheritedWidget ScopedModel Redux Fish-Redux BLoC / Rx GetIt https://docs. fl utter.dev/development/data-and-backend/state-mgmt

Slide 12

Slide 12 text

Flutterঢ়ଶ؅ཧͷͨ͘͞Μͷํ๏ StatefulWidget Provider Riverpod InheritedWidget ScopedModel Redux Fish-Redux BLoC / Rx GetIt https://docs. fl utter.dev/development/data-and-backend/state-mgmt ͷதͰ΋ಛʹຊےͷྲྀΕ͕෼͔Γͦ͏ͳͱ͜ΖΛϐοΫΞοϓ

Slide 13

Slide 13 text

Ͱ͸࣮ࡍʹͦΕͧΕͷίʔυΛݟ͍͖ͯ·͠ΐ͏

Slide 14

Slide 14 text

ͦͷલʹ…

Slide 15

Slide 15 text

Flutter is declarative

Slide 16

Slide 16 text

https:// fl utter.dev/docs/development/data-and-backend/state-mgmt/declarative Flutter is declarative =Flutter͸એݴతUIͰ͢ Flutter is declarative is licensed under Creative Commons Attribution 4.0 International License,

Slide 17

Slide 17 text

Sample CodeͷΠϝʔδ Football API APIΛୟ͘ औಘͨ͠σʔλΛҰཡͰදࣔ https://github.com/entaku0818/football_ fl utter

Slide 18

Slide 18 text

Ͱ͸࣮ࡍʹͦΕͧΕͷίʔυΛݟ͍͖ͯ·͠ΐ͏

Slide 19

Slide 19 text

StatefulWidget

Slide 20

Slide 20 text

StatefulWidget •Flutter ϦϦʔε౰ॳ(2018/12)͔Β͋Δ •WidgetʹState͕ηοτʹͳ͍ͬͯΔ https://api. fl utter.dev/ fl utter/widgets/StatefulWidget-class.html

Slide 21

Slide 21 text

https://api. fl utter.dev/ fl utter/widgets/StatelessWidget-class.html https://api. fl utter.dev/ fl utter/widgets/StatefulWidget-class.html class GreenFrog extends StatelessWidget { const GreenFrog({ Key? key }) : super(key: key) ; @override Widget build(BuildContext context) { return Container(color: const Color(0xFF2DBD3A)) ; } } class YellowBird extends StatefulWidget { const YellowBird({ Key? key }) : super(key: key) ; @override State createState() => _YellowBirdState() ; } class _YellowBirdState extends State { @override Widget build(BuildContext context) { return Container(color: const Color(0xFFFFE306)) ; } } StatefulWidget - ެࣜͷࣄྫ

Slide 22

Slide 22 text

class HomeScreen extends StatefulWidget { HomeScreen({Key key}) : super(key: key) ; ɹ// StateΛࢦఆ _HomeScreenState createState() => _HomeScreenState() ; } class _HomeScreenState extends State { ɹ ɹ// νʔϜҰཡΛఆ͍ٛͯ͠Δ List _teams = [] ; void initState() { // StateͷॳظԽॲཧ super.initState() ; fetch(); // ը໘ΞΫηε࣌ʹσʔλΛऔಘͯ͘͠Δ } StatefulWidget - SampleCode

Slide 23

Slide 23 text

@overrid e Widget build(BuildContext context) { return Scaffold ( appBar: AppBar ( title: const Text('νʔϜҰཡ') , ) , body: ListView.builder ( itemBuilder: (BuildContext context, int index) { return Card ( child: ListTile ( title: Text(_teams[index].name) , ) , ) ; } , itemCount: _teams.length , ) , ) ; } Future> fetch() async { final _teamsRepository = TeamsRepository() ; final result = await _teamsRepository.feachTeams() ; result.when ( success: (teams) { setState(() { _teams = teams ; }) ; } , failure: (error) { // Τϥʔॲཧ } , ) ; } StatefulWidget - SampleCode

Slide 24

Slide 24 text

ϝϦοτ σϝϦοτ w σʔλΛ7JFXʹ࣋ͨͳ͚Ε͹ͳΒͳ͍ w ࣄྫͰ͸νʔϜҰཡͷσʔλΛ8JEHFUͱಉ͡৔ॴʹఆٛ w ͋ΒΏΔॲཧ͕7JFXʹॻ͔ΕΔͷͰ'BU7JFXʹͳΔ w ࣄྫͰ͸νʔϜҰཡͷσʔλऔಘॲཧΛ8JEHFUͱಉ͡৔ॴʹఆٛ w Ұը໘Ͱॲཧ͕ॻ͚ΔͷͰ෼͔Γ΍͍͢ StatefulWidget

Slide 25

Slide 25 text

ScopedModel

Slide 26

Slide 26 text

ScopedModel •2018/11 ʹversion 1 •࠷ۙ͸ߋ৽͞Ε͍ͯͳ͍ •WidgetͱModelͷ෼཭ https://pub.dev/packages/scoped_model https://pub.dev/packages/scoped_model/example

Slide 27

Slide 27 text

// Note: It must extend from Model. class CounterModel extends Model { int _counter = 0 ; int get counter => _counter ; void increment() { // First, increment the counter _counter++ ; // Then notify all the listeners. notifyListeners() ; } } ScopedModel - ެࣜͷࣄྫ ঢ়ଶΛ΋ͭ.PEFMΛఆٛ

Slide 28

Slide 28 text

class CounterApp extends StatelessWidget { @override Widget build(BuildContext context) { // First, create a `ScopedModel` widget. This will provide // the `model` to the children that request it. return new ScopedModel ( model: new CounterModel() , child: new Column(children: [ // Create a ScopedModelDescendant. This widget will get the // CounterModel from the nearest ScopedModel. // It will hand that model to our builder method, and rebuild // any time the CounterModel changes (i.e. after we // `notifyListeners` in the Model). new ScopedModelDescendant ( builder: (context, child, model) => new Text('${model.counter}') , ) , new Text("Another widget that doesn't depend on the CounterModel" ) ] ) ) ; } } ScopedModel - ެࣜͷࣄྫ ར༻͢Δ 4DPQFE.PEFMΛఆٛ .PEFMΛར༻͍ͨ͠8JEHFUΛ 4DPQFE.PEFM%FTDFOEBOUͰ แΉ

Slide 29

Slide 29 text

class ScopedModelHome extends StatelessWidget { @overrid e Widget build(BuildContext context) { // StatelessWidgetͰఆٛ͠ModelΛݺͼग़͢=> ViewʹϏδωεϩδοΫ͕ೖͬͯ͜ͳ͍ return ScopedModel ( model: TeamScopedModel() , child: ScopedModelDescendant ( // ScopedModelDescendant഑ԼͰϞσϧΛࢀর builder: (context, child, model) => Scaffold ( appBar: AppBar ( title: const Text('νʔϜҰཡ') , ) , drawer: Header() , body: ListView.builder ( itemBuilder: (BuildContext context, int index) { return Card ( child: ListTile ( title: Text(model.teams[index].name) , ) , ) ; } , itemCount: model.teams.length , ) , ) , ) , ) ; } } ScopedModel - SampleCode

Slide 30

Slide 30 text

class TeamScopedModel extends Model { List _teams = [] ; List get teams => _teams ; TeamScopedModel() { // ॳճ࣮ߦ࣌ʹσʔλऔಘΛ࣮ߦ fetch() ; } Future> fetch() async { final _teamsRepository = TeamsRepository() ; final result = await _teamsRepository.feachTeams() ; result.when ( success: (teams) { _teams = teams ; // σʔλߋ৽࣌ʹΠϕϯτൃՐ notifyListeners() ; } , failure: (error) { // Τϥʔॲཧ } , ) ; } } ScopedModel - SampleCode

Slide 31

Slide 31 text

ϝϦοτ σϝϦοτ w ࠷ޙͷߋ৽͕ w ·ͩ/VMM4BGFUZʹ΋ରԠ͍ͯ͠ͳ͍ w ࠓޙߋ৽͞ΕΔՄೳੑ͸௿͍ʁ w w 4UBUFGVM8JEHFUͱൺֱͯ͠ϏδωεϩδοΫΛ෼཭͠΍͍͢ ScopedModel - SampleCode

Slide 32

Slide 32 text

Provider

Slide 33

Slide 33 text

https://pub.dev/packages/provider •2018/10 ver1.0.0 •͜ͷ࣌ظʹ࢖ͬͯͨਓ͍Δͷ͔ͳ.. •ެࣜαΠτͰਪ঑͞Ε͍ͯΔ΍Γํ https://docs. fl utter.dev/development/data-and-backend/state-mgmt/simple https://pub.dev/packages/provider Provider

Slide 34

Slide 34 text

void main() { runApp ( ChangeNoti fi erProvider ( create: (context) => CartModel(), // ChangeNoti fi e r child: const MyApp(), // Widge t ) , ) ; } Provider - ެࣜͷࣄྫ $IBOHF/PUJ fi FS1SPWJEFS Ͱ$IBOHF/PUJ fi FSͱ8JEHFUΛ ఆٛ

Slide 35

Slide 35 text

class CartModel extends ChangeNoti fi er { /// Internal, private state of the cart. fi nal List _items = [] ; /// An unmodi fi able view of the items in the cart. Unmodi fi ableListView get items => Unmodi fi ableListView(_items) ; /// The current total price of all items (assuming all items cost $42). int get totalPrice => _items.length * 42 ; /// Adds [item] to cart. This and [removeAll] are the only ways to modify the /// cart from the outside. void add(Item item) { _items.add(item) ; // This call tells the widgets that are listening to this model to rebuild. notifyListeners() ; } /// Removes all items from the cart. void removeAll() { _items.clear() ; // This call tells the widgets that are listening to this model to rebuild. notifyListeners() ; } } Provider - ެࣜͷࣄྫ $IBOHF/PUJ fi FSͰ஋Λఆٛ

Slide 36

Slide 36 text

return Consumer ( builder: (context, cart, child) { return Text("Total price: ${cart.totalPrice}") ; } , ); Provider - ެࣜͷࣄྫ $POTVNFSͰғ͏͜ͱͰ $IBOHF/PUJ fi FSൃՐ࣌ʹ 8JEHFUΛߋ৽

Slide 37

Slide 37 text

Provider - SampleCode '/changeNotifier': (BuildContext context) => ChangeNotifierProvider ( create: (context) => TeamModel(),ɹ// ChangeNotifie r child: ChangeNotifierHome(), // Widge t ) ,

Slide 38

Slide 38 text

Provider - SampleCode class ChangeNotifierHome extends StatelessWidget { @overrid e Widget build(BuildContext context) { return Scaffold ( appBar: AppBar ( title: const Text('νʔϜҰཡ') , ) , drawer: Header() , // Widget಺ͷߋ৽ൣғΛConsumerͰғ͏ body: Consumer ( builder: (BuildContext context, TeamModel value, Widget child) { return ListView.builder ( itemBuilder: (BuildContext context, int index) { return Card ( child: ListTile ( title: Text(value.teams[index].name) , ) , ) ; } , itemCount: value.teams.length , ) ; })) ; } }

Slide 39

Slide 39 text

Provider - SampleCode class TeamModel extends ChangeNotifier { List _teams = [] ; List get teams => _teams ; TeamModel() { // ॳظԽ࣌ʹσʔλऔಘ fetch() ; } Future> fetch() async { final _teamsRepository = TeamsRepository() ; final result = await _teamsRepository.feachTeams() ; result.when ( success: (teams) { _teams = teams ; //ɹมߋΛ௨஌ notifyListeners() ; } , failure: (error) { //ɹΤϥʔॲཧ } , ) ; } }

Slide 40

Slide 40 text

Provider w ෳ਺ͷ1SPWJEFSͱ8JEHFUͷ૊Έ߹ΘͤΛར༻͢Δ৔߹ ը໘਺͕ଟ͍ෳࡶͳը ໘ભҠ ʹΤϥʔʹͳͬͯ͠·͏ ϝϦοτ σϝϦοτ w 1SPWJEFSͱ8JEHFUͷ૊Έ߹Θͤґଘੑͷ஫ೖͱঢ়ଶ؅ཧ͕ߦ͑Δ '/changeNotifier': (BuildContext context) => ChangeNotifierProvider ( create: (context) => TeamModel() , child: ChangeNotifierHome() , ) ,

Slide 41

Slide 41 text

Riverpod

Slide 42

Slide 42 text

https://pub.dev/packages/provider •2021/11/5ʹ ver1.0.0 •Providerͷ໰୊఺Λղফ͢ΔܗͰ஀ੜ https://pub.dev/packages/riverpod https://riverpod.dev RiverPod

Slide 43

Slide 43 text

fi nal counterProvider = StateNoti fi erProvider((ref) { return Counter() ; }) ; class Counter extends StateNoti fi er { Counter(): super(0) ; void increment() => state++ ; } Riverpod - ެࣜͷࣄྫ άϩʔόϧʹ1SPWJEFSΛఆٛ

Slide 44

Slide 44 text

class Example extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { fi nal count = ref.watch(counterProvider) ; return Text(count.toString()) ; } } Riverpod - ެࣜͷࣄྫ $POTVNFS8JEHFUͰ8JEHFUΛੜ੒ ͠ɺ SFGXBUDIͰ1SPWJEFSͷঢ়ଶΛ؅ཧ

Slide 45

Slide 45 text

Riverpod // ProviderScopeͰର৅ͷWidgetΛғ͏ '/RiverpodHome': (BuildContext context) => ProviderScope ( child: RiverpodHome() , )

Slide 46

Slide 46 text

Riverpod // TeamStateNotifierΛࢀর͠ɺάϩʔόϧʹެ։ final TeamStateNotifierProvider = StateNotifierProvider> ( (ref) => TeamStateNotifier([]) , ) ; class TeamStateNotifier extends StateNotifier> { TeamStateNotifier(List state) : super(state) { // ॳظԽ࣌ʹσʔλऔಘ _fetch() ; } Future> _fetch() async { final _teamsRepository = TeamsRepository() ; final result = await _teamsRepository.feachTeams() ; result.when ( success: (teams) { state = teams ; } , failure: (error) { // Τϥʔॲཧ } , ) ; } }

Slide 47

Slide 47 text

Riverpod class RiverpodHome extends ConsumerWidget { @overrid e Widget build(BuildContext context, WidgetRef ref) { // ref.watchͰProviderͷঢ়ଶΛࢀর final teams = ref.watch(TeamStateNotifierProvider) ; return Scaffold ( appBar: AppBar ( title: const Text('νʔϜҰཡ') , ) , drawer: Header() , body: ListView.builder ( itemBuilder: (BuildContext context, int index) { return Card ( child: ListTile ( title: Text(teams[index].name) , ) , ) ; } , itemCount: teams.length , ) , ) ; } }

Slide 48

Slide 48 text

Riverpod ϝϦοτ σϝϦοτ w 1SPWJEFSΛάϩʔόϧͰఆٛՄೳͰͲͷ8JEHFU͔Β΋࢖͑Δ w ແཧ໼ཧ͋͛ΔͳΒɺ ϦϦʔεͨ͠͹͔ΓͰมߋͷՄೳੑ͕͋Δ w 1SPWJEFS͸WFSTJPO·Ͱग़͍ͯΔ

Slide 49

Slide 49 text

• ·ͣγϯϓϧͳΞϓϦΛ࡞ΔͳΒStatefulWidget͕͍͍ • ܦݧऀ΋ଟ͍ͷͰ࣮ྫ΋͋Δ͸ͣ • ScopedModel͸ߋ৽͞Εͯͳ͍Α͏ͩ͠ࠓޙ͸࢖͏΂͖Ͱ͸ͳ ͦ͞͏ • ෳ਺ը໘Ͱঢ়ଶΛڞ༗͢Δ৔߹͸Provider΍RiverpodΛ࢖༻͢Δ • ҆ఆੑ͕ཉ͍͠ͳΒProvider • ը໘ભҠ͕ෳࡶͳΒRiverpod ·ͱΊ

Slide 50

Slide 50 text

ΈΜͳ͸Կ࢖͍ͬͯΔΜͩΖ͏ʁ 🤔

Slide 51

Slide 51 text

ΞϯέʔτͱͬͯΈ·ͨ͠ʂ 11/19࣌఺ 19ճ౴ https://docs.google.com/forms/d/e/ 1FAIpQLSccB2iZJ78FeLPj1ST31FVUxj73Tc4d-0KhXpbJnT-l9kYZMQ/viewform

Slide 52

Slide 52 text

ݩʑωΠςΟϒ։ൃऀ͕໿7ׂ Flutter͔Β͸3ׂ

Slide 53

Slide 53 text

શͯͷਓ͕StatefulWidgetΛ࢖ͬͯͨ BLoC΍Redux͸ͦΜͳʹ࢖ΘΕ͍ͯͳ͍ʁ

Slide 54

Slide 54 text

StatefulWidget͸ গͣͭ͠࢖ΘΕͳ͘ͳ͖͍ͬͯͯΔ Riverpod͕த৺͔ʁ

Slide 55

Slide 55 text

ࠓޙ͸RiverpodΛ࢖͍͖͍ͬͯͨਓ͕ଟ਺

Slide 56

Slide 56 text

࣮ྫͱྺ࢙͔ΒֶͿʂ Flutterͷঢ়ଶ؅ཧํ๏ͷ બఆ

Slide 57

Slide 57 text

࣮ྫͱྺ࢙͔ΒֶͿʂ Flutterͷঢ়ଶ؅ཧํ๏ͷ બఆ ʴະདྷ

Slide 58

Slide 58 text

https://speakerdeck.com/aomathwift/ji-neng-gotonidong-zuo- suruminiapuridepurebiyusaikuruwobao-su-nisitahua ઌ೔ͷiOSDCͰ͋ͬͨϛχΞϓϦͷ࿩ https://speakerdeck.com/aomathwift/ji-neng-gotonidong-zuo- suruminiapuridepurebiyusaikuruwobao-su-nisitahua

Slide 59

Slide 59 text

ઌ೔ͷiOSDCͰ͋ͬͨϛχΞϓϦͷ࿩ https://techlife.cookpad.com/entry/2021/06/16/110000

Slide 60

Slide 60 text

কདྷతʹ StatefulWidget γϯϓϧͳΞϓϦΛ࡞Δ (ը໘಺Ͱ׬݁) Provider ෳࡶͳΞϓϦΛ࡞Γ͍ͨ ଟ͘ͷը໘ભҠ͕͋Δ Riverpod ? ϚϧνϞδϡʔϧ Խͯ͠ϛχΞϓϦ Λ࡞Δ

Slide 61

Slide 61 text

Ξϯέʔτͷ݁Ռ͔Β΋ࠓޙ΋ͬͱ ΋ͬͱFlutterͰ࡞ΔΞϓϦͷෳࡶ͞͸ ૿͍ͯ͘͠ͷͰ͸ʁ🤔

Slide 62

Slide 62 text

ϗϯτͷ·ͱΊ • ·ͣγϯϓϧͳΞϓϦΛ࡞ΔͳΒStatefulWidget͕͍͍ • ܦݧऀ΋ଟ͍ͷͰ࣮ྫ΋͋Δ͸ͣ • ScopedModel͸ߋ৽͞Εͯͳ͍Α͏ͩ͠ࠓޙ͸࢖͏΂͖Ͱ͸ͳ ͦ͞͏ • ෳ਺ը໘Ͱঢ়ଶΛڞ༗͢Δ৔߹͸Provider΍RiverpodΛ࢖༻͢Δ • ҆ఆੑ͕ཉ͍͠ͳΒProvider • ը໘ભҠ͕ෳࡶͳΒRiverpod • (iOS։ൃͷࣄྫ͔Β)ϚϧνϞδϡʔϧԽͯ͠ϛχΞϓϦΛ࡞Δ͜ͱ ͕ٻΊΒΕΔΑ͏ʹͳΔՄೳੑ

Slide 63

Slide 63 text

ࢀߟ • Flutter SDKެࣜαΠτ- https://api. fl utter.dev/ • Flutter Developer ެࣜυΩϡϝϯτ - https:// fl utter.dev/development • iOSΞϓϦઃܭύλʔϯೖ໳ - https://peaks.cc/books/iOS_architecture • iOSDC ػೳ͝ͱʹಈ࡞͢ΔϛχΞϓϦͰϓϨϏϡʔαΠΫϧΛര଎ʹͨ͠࿩ - https://speakerdeck.com/aomathwift/ji-neng-gotonidong-zuo- suruminiapuridepurebiyusaikuruwobao-su-nisitahua • Flutterͷঢ়ଶ؅ཧख๏ͷબఆ - https://medium.com/ fl utter-jp/ state-1daa7fd66b94 • ίʔυੜ੒Λ༻͍ͨiOSΞϓϦϚϧνϞδϡʔϧԽͷͨΊͷґଘղܾ - https://techlife.cookpad.com/entry/2021/06/16/110000 • Flutter Ͱ࡞ΒΕͨ༑ਓͷΞϓϦ - ⭐ https://github.com/bannzai/Pilll ⭐

Slide 64

Slide 64 text

ϑΟʔυόοΫ͍ͩ͘͞ʂ

Slide 65

Slide 65 text

࣮ྫͱྺ࢙͔ΒֶͿʂ Flutterͷঢ়ଶ؅ཧํ๏ͷબఆ FlutterKaigi 2021 11/29 19:45ʙ entaku / @entaku_0818