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

Flutter 2021 の振り返りと今後のアプリ開発に向けて / Looking back on Flutter 2021 and for future app development.

Flutter 2021 の振り返りと今後のアプリ開発に向けて / Looking back on Flutter 2021 and for future app development.

Daichi Furiya (Wasabeef)

December 11, 2021
Tweet

More Decks by Daichi Furiya (Wasabeef)

Other Decks in Programming

Transcript

  1. Flutter 2021 ͷৼΓฦΓͱࠓޙͷΞϓϦ։ൃʹ޲͚ͯ 1. ΫϩεϓϥοτϑΥʔϜͷݱঢ়ͱ՝୊ 2. 2022೥΋ Flutter Λબ୒͢Δ͔Ͳ͏͔ 3.

    Flutter ͱ Dart ͷΞοϓσʔτ 4. Flutter ΞϓϦ։ൃͷࠓޙʹ޲͚ͯ 4.1. ΞϓϦͷΞʔΩςΫνϟ 4.2. React ͔Βֶͼ͍ͨ͜ͱ
  2. ΫϩεϓϥοτϑΥʔϜͰ։ൃ͢Δલʹ஌͓ͬͯ͘΂͖͜ͱ • ΫϩεϓϥοτϑΥʔϜͰ։ൃ͢ΔҙຯΛߟ͑Δ 
 → ԿΛڞ௨Խ͍͔ͨ͠ʁ • ʮͱΓ͋͑ͣ Flutter ͕ྲྀߦͬͯΔΒ͍͔͠Β

    Flutter Ͱ΍ͬͱ͚ ͹͍͍ʯͰ͸ͳ͍ • ͜Ε·Ͱొ৔͖ͯͨ͠਺ଟͷΫϩεϓϥοτϑΥʔϜϑϨʔϜ ϫʔΫ͕ਁಁ͠ͳ͔ͬͨ͜ͱ اۀͱͯ͠औΓ૊Ή৔߹
  3. ϝϦοτ • ΤϯδχΞͷϦιʔεઅ໿ • ਓ਺ɾ։ൃظؒ୹ॖ • ։ൃίϛϡχςΟͷٸ଎తͳ ֦େʢࠃ಺֎ɺFlutterKaigi ͳͲʣ •

    iOS/Android Ͱڞ௨ͷ UI 
 ʢྑ͘΋ʣ σϝϦοτ • ΞϓϦಈ࡞ͷ҆ఆੑ 
 ʢωΠςΟϒʹ͸ྼΔʣ • কདྷੑɺઌߦ͖ෆ҆ 
 ʢͲͷϑϨʔϜϫʔΫΛ࠾༻͢Δ͔ʣ • iOS/Android Ͱڞ௨ͷ UI 
 ʢѱ͘΋ʣ • iOS/Android ͷ৽ػೳ΁ͷ௥ ैͷ஗ΕΔ
  4. Compose Multiplatform • 1.0.0 (Dec 2021) • Jetpack Compose ϕʔεͰ

    Jetbrains ʹΑΔ։ൃ • ݩʑ͸ Compose for Desktop ͩͬͨ΋ͷ • ݱࡏ͸ओʹ Desktop / Web ΞϓϦ޲͚ʢAndroid Ͱ΋ಈ͘ʣ • ݱஈ֊Ͱ͸ iOS ΁ͷެࣜαϙʔτ͸ൃද͞Ε͍ͯͳ͍ • άϥϑΟοΫΤϯδϯ͸ Skia Λར༻͍ͯ͠Δ
  5. Skia • άϥϑΟοΫϥΠϒϥϦ • Android, Chrome, Firefox, Blink, Flutter Ͱ࠾༻͞Ε͍ͯΔ

    • Skia ͸ Vulkan ΛόοΫΤϯυΤϯδϯͱͯ͠มߋՄೳ • Android 9 Ҏ߱ɺσϑΥϧτͷϨϯμϥʔ͸ Skia ʹͳ͍ͬͯΔ ʢ8 ͱ 9 ͰUI͕มΘͬͨͷ͸ͦͷͨΊʣɻFlutter ʹ͢ΔͱϨΠ Ϡʔ͕૿͑Δ΋ͷͷϨϯμϥʔ͸ಉ͡
  6. Skia • ΋ͪΖΜ iOS Ͱ΋ Windows Ͱ΋ಈ࡞͢Δ ରԠϓϥοτϑΥʔϜ • Windows

    7, 8, 8.1, 1 0 • macOS 10.10.5 or late r • iOS 8 or late r • Android 4.1 (JellyBean) or late r • Ubuntu 14.04+, Debian 8+, openSUSE 13.3+, or Fedora Linux 24+
  7. ΫϩεϓϥοτϑΥʔϜ͔ʁ֤ϓϥοτϑΥʔϜʢiOS / Androidʣ͔ʁ ɹΫϩεϓϥοτϑΥʔϜɹ ɹ֤ϓϥοτϑΥʔϜʢiOS / Androidʣ • UI ΍ϏδωεϩδοΫ͕

    iOS / Android Ͱҧ͏ • ҆ఆͯ͠ΫΦϦςΟ͕ߴ͍΋ͷ࡞ Γ͍ͨ • iOS / Android ͷܦݧऀ͕͍Δ • εέδϡʔϧʹ༨༟͕͋Δ ྫ ྫ • UI ΍ϏδωεϩδοΫ͕ iOS / Android Ͱಉ͡ • ٕज़౤ࢿతͳνϟϨϯδΛ͍ͨ͠ • iOS / Android ͷܦݧऀ͕গͳ͍ 
 ʢΤϯδχΞͷ਺͕গͳ͍ʣ • εέδϡʔϧʹ༨༟͕ͳ͍
  8. ΞϓϦͷػೳཁ݅͸ʁ • iOS ͱ Android Ͱ UI ͷମݧ͕શ͘ҧ͏ʁ 
 →

    Flutter ͸ཱͪҐஔ͕ UI ϑϨʔϜϫʔΫͳͷͰཁ͕݅ͦ΋ͦ΋͍͋ͬͯͳ͍͔΋ • ಈըͳͲͷίϯςϯπΛදࣔ͢Δͷʹ DRM ͕ඞཁʁ 
 → Flutter ͚ͩͰ࣮૷͢Δͷ͸ݱஈ֊Ͱ͸ FairPlay ରԠͨ͠ϥΠϒϥϦ͕ͳ͍ • οοΰϦοοΰϦͷΞχϝʔγϣϯ͕͋Δʁ 
 → ύϑΥʔϚϯε͕ωΠςΟϒΑΓྼΔͷͰཁݕূ
  9. React Native ͸ʁ • Web ํ໘ʹڧ͍ΤϯδχΞ͕͍Δ৔߹ͳͲͷબ୒ࢶ 
 → νʔϜͷಘҙྖҬ΍ٕज़ࢿ࢈ʹΑܾͬͯఆ •

    ϝϧΧϦ US ͕ React Native Ͱॻ͖௚ͨ͠Β͍͠ 
 → https://link.medium.com/SNFEPzc1Rlb • Facebook ΍ Coinbase ΍ Discord ΍ Tesla ͱ͍ͬͨ༗໊ͳاۀ΋࠾༻ 
 → https://reactnative.dev/showcase • npm ʹ͋Δࢿ࢈Λࢧ͑Δͷ͸ڧΈ 
 ʢ༨ஊɿϒϩοΫνΣʔϯؔ࿈ٕज़͸ React Native ޲͚ͷ SDK ʹ͔͠ରԠ͍ͯ͠ͳ͍৔߹͕ଟ͍ʣ
  10. .NET MAUI ʢچ Xamarin.Formsʣ͸ʁ • C# ํ໘ʹڧ͍ΤϯδχΞ͕͍Δ৔߹ͳͲͷબ୒ࢶ 
 → νʔϜͷಘҙྖҬ΍ٕज़ࢿ࢈ʹΑܾͬͯఆ

    • ৽ܕίϩφ΢Πϧε઀৮֬ೝΞϓϦ͸ Xamarin Λ࠾༻ • .NET MAUI ʹͳΔͱ XML ΛࣙΊએݴత UI Ͱॻ͚Δ • ࣄྫ͕ Flutter ΍ React Native ʹൺ΂Δͱগͳ͍
  11. Compose Multiplatform ͷ୆಄͸ʁ • ୆಄͢Δ͔΋͠Εͳ͍͕ɺͦΕ͕ 2022 ೥ʹϞόΠϧ޲͚ ʹ Production Ready

    ʹͳΔՄೳੑ͸௿͍ 
 → ݱஈ֊Ͱ iOS ͷਖ਼ࣜαϙʔτ͕ೖ͍ͬͯͳ͍ • ͦͷ൑அ͕Ͱ͖Δͷ͕΋͏গ͠ઌʹͳΓͦ͏
  12. • ৽نϓϩδΣΫτ • গਓ਺ʢ1ਓʙʣ • গਓ਺Ͱ΋྆ϓϥοτϑΥʔ ϜͰϒϥϯσΟϯάͷҰ؏ੑ Λҡ͍࣋ͨ͠ • ͱʹ͔͘ૣ͘ϦϦʔε͍ͨ͠

    • ΑΓνϟϨϯδ͍ͨ͠ ྫ ɹFlutterɹ Flutter ͔ʁ Kotlin MPP ͔ʁ+ Compose Multiplatform ͔ʁ ɹKotlin MPPɹ • ৽ن / طଘϓϩδΣΫτ • େਓ਺ʢ10ਓʙʣ • iOS/AndroidΞϓϦ։ൃͷਫ਼ ௨͍ͯ͠Δ • ϦεΫΛ཈͍֤͑ͨϓϥοτ ϑΥʔϜͰ෼͚ͯσβΠϯΛ ߆Γ͍ͨ ྫ ɹ+ Compose Multiplatformɹ • ݕূʢ৽نʣϓϩδΣΫτ • গਓ਺Ͱ΋྆ϓϥοτ ϑΥʔϜͰϒϥϯσΟϯά ͷҰ؏ੑΛҡ͍࣋ͨ͠ • ͱʹ͔͘ૣ͘ϦϦʔεͨ͠ ͍ • কདྷ΁ͷٕज़౤ࢿΛ͍ͨ͠
  13. • UI ΋ϏδωεϩδοΫ΋ڞ ௨ԽͰ͖Δ • ࣄྫ͕ଟ͍ • Google Λ৴͍ͯ͡Δ ྫ

    ɹFlutterɹ Kotlin MPP + Compose ɹɹɹɹɹɹ Multiplatform • Web / React ϝϯόʔ͕ଟ͍ 
 ʢWeb ͷࢿ࢈Λ࢖͍͍ͨʣ • UI ΋ϏδωεϩδοΫ΋ڞ ௨ԽͰ͖Δ • ࣄྫ͕ଟ͍ʢ͕ࠃ಺ͷ੎͍͸ྼΔʣ • Facebook Λ৴͍ͯ͡Δ • Android / Kotlin ϝϯόʔ͕ ଟ͍ʢKotlin ͷࢿ࢈Λ࢖͍͍ͨʣ • ॳظஈ֊͸ϏδωεϩδοΫ ͚ͩڞ௨Խ͠ Compose Multiplatform ͕҆ఆͨ͠λ ΠϛϯάͰ UI Λॻ͖௚͢ύ λʔϯ • Jetbrains Λ৴͍ͯ͡Δ ྫ React Native ྫ 2022೥΋ Flutter Λબ୒͢Δ͔Ͳ͏͔
  14. Flutter 2.8 1. ύϑΥʔϚϯε޲্
 ʢGoogle Pay ͰىಈςετͰ 10 ʙ 50%

    ͷ޲্ʣ 2. Android systrace ʹ Flutter ͷύϑΥʔϚϯετϨʔεΛૹ৴͢ΔΑ͏ʹมߋ 3. Flutter DevTools ʹ Enhance Tracing ௥ՃʢUI δϟϯΫ਍அͳͲʹ༗ޮʣ 4. FirebaseʢFlutterFireʣPlugin ͷ҆ఆ൛ϦϦʔε 4.1. Firebase UI ʢೝূ΢ΟδΣοτʣͷϦϦʔε 5. Desktop ൛ͷߋ৽ʢ׽ࣈ IME αϙʔτͳͲʣ 6. Dev channel ͷഇࢭʢࠓޙ͸ master, beta, master Ͱӡ༻ʣ https://link.medium.com/a7Md15TNPlb
  15. Dart 2.15 1. Isolate class ͷػೳ޲্ 2. Constructor tear-off s

    3. enum ʹ API ௥Ճ 4. ϥΠϒϥϦ։ൃ޲͚Ͱ pub ʹػೳ௥Ճ https://link.medium.com/kZTQCmukPlb
  16. Isolate ͷػೳ޲্ લఏͱͯ͠ɿDart Ͱ 16ms Λ௒͑ͯϑϨʔϜϨʔτΛམͱ͢Α͏ͳॲཧΛ͢Δ ৔߹ʹ͸ Isolate Λར༻ͯ͠ Worker

    Isolate Ͱ࣮ߦ͢Δ͜ͱΛਪ঑͞Ε͍ͯΔ ʢωοτϫʔΫϨεϙϯεΛͨͩ଴͍ͬͯΔ͚ͩͷΑ͏ͳॲཧͷ৔߹ͦͷݶΓ Ͱ͸ͳ͍ʣ Dart 2.15Ͱ͸ Worker Isolate ͔ΒϝϞϦΛίϐʔͤͣʹ Isolate.exit() Ͱ݁ՌΛ ฦ͢͜ͱ͕Ͱ͖Δɻ Flutter ͷ compute Λطʹར༻͍ͯ͠Δ৔߹͸ Flutter 2.8 ͷΞοϓάϨʔυ͢Δ͜ͱͰࣗಈతʹύϑΥʔϚϯε͕޲্͢Δ https://link.medium.com/kZTQCmukPlb
  17. https://link.medium.com/kZTQCmukPlb // Isolate.spawn final p = ReceivePort(); await Isolate.spawn(_read, p.sendPort);

    await p.first; // Isolate.exit Future _read(SendPort p) async { final fileData = await File(filename).readAsString(); final jsonData = jsonDecode(fileData); Isolate.exit(p, jsonData); } final fileData = File(fileName); // compute compute(_read, jsonData); List<SearchResult> _read(File filename) { final fileData = await File(filename).readAsString(); final jsonData = jsonDecode(fileData); } Isolate ͷػೳ޲্ Flutter ͷ computeʢத਎͸ Dart ͷ Isolateʣ Dart ͷ Isolate
  18. Isolate ͷػೳ޲্ https://link.medium.com/kZTQCmukPlb void main() async { final jsonData =

    await _parseInBackground(); print('number of JSON keys = ${jsonData.length}'); } // Isolate.spawn Future<Map<String, dynamic>> _parseInBackground() async { final p = ReceivePort(); await Isolate.spawn(_readAndParseJson, p.sendPort); return await p.first; } // Isolate.exit Future _readAndParseJson(SendPort p) async { final fileData = await File(filename).readAsString(); final jsonData = jsonDecode(fileData); Isolate.exit(p, jsonData); }
  19. Constructor tear-offs https://link.medium.com/kZTQCmukPlb class Hoge { Hoge() { print(‘Called Hoge()’);

    } } void main() { const hoge = Hoge.new; hoge(); } // Lambda names.forEach((name) { print(name); }); // Tear-off names.forEach(print);
  20. Constructor tear-offs https://link.medium.com/kZTQCmukPlb class FruitWidget extends StatelessWidget { @override Widget

    build(BuildContext context) { return Column( children: ['Apple', 'Orange'].map((value) => Text(value)).toList()); } } class FruitWidget extends StatelessWidget { @override Widget build(BuildContext context) { return Column( children: ['Apple', 'Orange'].map(Text.new).toList()); } } Ҏલ͸ .map ʹϥϜμΛ౉͍͕ͯͨ͠ ίϯετϥΫλͰ΋ tear-off Ͱ͖ΔΑ͏ʹͳͬͨ
  21. enum ʹ API ௥Ճ https://link.medium.com/kZTQCmukPlb enum MyEnum { one, two,

    three } void main() { // .name Ͱྻڍ໊ͷऔಘ -> "one" print(MyEnum.one.name); } / / .byName Ͱྻڍ஋ͷݕࡧ print(MyEnum.values.byName('two') == MyEnum.two); / / .asNameMap Ͱ໊ͱ஋ͷ Map Λऔಘ final map = MyEnum.values.asNameMap(); print(map['three'] == MyEnum.three);
  22. ϦϦʔε࣌ʹҙਤ͠ͳ͍ Secrets ؚ͕·Εͯͳ͍͔νΣοΫ https://link.medium.com/kZTQCmukPlb Publishing my_package 1.0.0 to https://pub.dartlang.org: Package

    validation found the following errors: * line 1, column 1 of lib/key.pem: Potential leak of Private Key detected. ╷ 1 │ ┌ - - -BEGIN PRIVATE KEY - - - 2 │ │ H0M6xpM2q+53wmsN/eYLdgtjgBd3DBmHtPilCkiFICXyaA8z9LkJ 3 │ └ - - -END PRIVATE KEY - - - ╵ * line 2, column 23 of lib/my_package.dart: Potential leak of Google OAuth Refresh Token detected. ╷ 2 │ fi nal refreshToken = "1//042ys8uoFwZrkCgYIARAAGAQSNwF-L9IrXmFYE-sfKefSpoCnyqEcsHX97Y90KY- p8TPYPPnY2IPgRXdy0QeVw7URuF5u9oUeIF0"; false_secrets: - /lib/src/hardcoded_api_key.dart - /test/localhost_certi fi cates/*.pem ྫɿdart pub publish ࣌ͷνΣοΫ ςετσʔλͱͯ͠μϛʔΛஔ͍͍ͯΔ৔߹ͳͲ͸ pubspec.yaml ʹҎԼΛ௥Ճ
  23. DartPad • React CodeSandbox ͷΑ͏ʹ Web ্Ͱ Dart ΍ Flutter

    ͷಈ࡞؀ڥΛ؆୯ʹ༻ҙͰ͖ΔʢCodeSandbox ΄ͲߴػೳͰ͸ͳ͍ʣ • Ұ෦ͷطʹ༻ҙ͞Ε͍ͯΔ༗໊ͳύοέʔδ΋ར༻͢Δ ͜ͱ͕Ͱ͖ΔʢFirebaseɺhttpɺriverpodɺFlutter Hooks ͳͲʣ
  24. Android ΤϯδχΞʹ͸ೃ છΈͷਂ͍Ͱ͋Ζ͏ MVVM + Repository Ͱ΋ Flutter ͰͦΕ͕࠷దղͱ ͸ݶΒͳ͍

    View Repository ViewModel Local Source Remote Data Source MVVM Repository MVVM + Repository
  25. ViewModel Λ࡞ͬͯ؅ཧ͢Δʁ ίϯϙʔωϯτͷ୯ҐΛࡉ͔͘͢Ε ͹ͦͷ෼͚ͩରʹͳΔViewModel ΋ ૿͑Δʁ ͜ͷϘλϯͷঢ়ଶ؅ཧ͸Ͳ͏͢Δʁ class ChatViewModel {

    String text = ''; bool enabled = false; } class ChatWidget extends HookConsumerWidget { const ChatWidget({Key? key}) : super(key: key); @override Widget build(BuildContext context, WidgetRef ref) { final viewModel = ref.watch(chatViewModelProvider); return Column( children: [ TextFormField( onChanged: (value) => viewModel.enabled = value.isNotEmpty, ), ElevatedButton( onPressed: viewModel.enabled ? () {/** Submit **/} : null, child: Text('Submit'), ), ], ); } }
  26. ίϯϙʔωϯτ਺ ≒ ViewModel਺ʁ • XML ࣌୅ͷ Android ͩͱϨΠΞ΢τͷ୯Ґ͕େ͖͔ͬͨ • Flutter

    Ͱ͸Ͱ͖Δ͚ͩίϯϙʔωϯτΛখ͘͞׬݁ʹ࡞ Γ͍ͨ • ϑΝΠϧ਺૿͑͗͢ΔͷͰίϯϙʔωϯτΛࡉ͔͘͢͠ ͗ͳ͍Α͏ʹ͢Δ͔ʁʢͦͷԘക͕೉͍͠ʣ
  27. class DetailViewModel { DetailData data; bool isLoading; String chatText =

    ''; bool submitButtonEnabled = false; Future<> fetchData(){ } Future<> postData() { } } class ChatWidget extends HookConsumerWidget { const ChatWidget({Key? key}) : super(key: key); @override Widget build(BuildContext context, WidgetRef ref) { final viewModel = ref.watch(chatViewModelProvider); return ... // TextField, Button } } ChatWidget ͸ςΩετϑΟʔϧυͱϘλϯ͔͠ͳ͍ͷʹɺ ຊདྷ͢Δඞཁͷͳ͍΋ͷ·Ͱ·Ͱ஌Δ͜ͱ͕Ͱ͖ͯ͠·͏ DetailViewModel ͸ ChatWidget Ͱ͸࢖ Θͳ͍σʔλऔಘ΍ϩʔσΟϯάঢ়ଶͳ Ͳ΋΋͍ͬͯΔ ViewModel ͷൣғΛগ͠େ͖ͯ͘͠ΈΔʁ
  28. ؆୯ͳঢ়ଶ͸ίϯϙʔωϯτଆͰ͍࣋ͪͨ ʴ ؆୯ʹॻ͖͍ͨ class CountPage extends StatefulWidget { @override State

    createState() => _CountState(); } class _CountState extends State<CountPage> { int count = 0; @override void initState() { /** ॳظԽ **/ } @override void dispose() { /** ഁغ **/ } void setCount(int value) => setState(() => count = value); @override Widget build(BuildContext context) { return TextButton( child: Text(“Count: $count"), onPressed: () => setCount(count + 1), ); } } Flutter Hooks Ͱॻ͘ class CountPage extends HookWidget { @override Widget build(BuildContext context) { final count = useState(0); return TextButton( child: Text("Count: ${count.value}"), onPressed: () => count.value++, ); } }
  29. LaunchedEffect(id) { // ID ͕มߋ͞ΕΔͨͼ } SideEffect { // ຖճ

    } LaunchedEffect(Unit) { // ࠷ॳͷҰճ } 73 useEffect useEffect(() => { // ID ͕มߋ͞ΕΔͨͼ }, [id]); useEffect(() => { // ຖճ }); useEffect(() => { // ࠷ॳͷҰճ }, const []); Flutter Hooks Jetpack Compose
  30. • React Hook ࢖ͬͯσʔλऔಘ͢Δ ͨΊͷϥΠϒϥϦ • ίϯϙʔωϯτଆͰ API ͷωοτ ϫʔΫϦΫΤετΛ͢Δ

    • Ωϟογϡ΍ϦΫΤετͷॏෳഉ আͳͲ͸ϥΠϒϥϦଆ͕ߦ͏ React Query / SWR import useSWR from 'swr' function Profile() { const { data, error } = useSWR('/api/user', fetcher) if (error) return <div>failed to load</div> if (!data) return <div>loading...</div> return <div>hello {data.name}!</div> }
  31. react-use • ศརͳ Custom Hooks ͕ͨ͘͞Μؚ·Ε͍ͯΔ ϥΠϒϥϦ • npm i

    react-use ͯ͠࢖ͬͯ΋͍͍͠ɺ Unlicense ͳͷͰҰ෦Λίϐϖͯ͠࢖ͬͯ΋͍͍
  32. import { useNetworkState } from 'react-use'; const Demo = ()

    => { const state = useNetworkState(); return ( <pre> {JSON.stringify(state, null, 2)} </pre> ); }; ྫ͑͹ useNetworkState Λ࢖͏ͱ୺ ຤ͷωοτϫʔΫঢ়ଶΛ؂ࢹͯ͠ঢ় ଶ͕มΘͬͨΒϦϏϧυͯ͠஋Λऔ ಘͰ͖ΔΑ͏ʹ͢Δ react-use
  33. react-hooks-testing-library test('should increment counter', () => { const { result

    } = renderHook(() => useCounter()) act(() => { result.current.increment() }) expect(result.current.count).toBe(1) }) ࣗ࡞ Hooks ͷςετΛॻ͘ʹ͸ίϯ ϙʔωϯτΛߋ৽͢ΔͨΊͷτϦ ΨʔΛॻ͍ͯ࠶ϨϯμϦϯάͤͨ͞ Γɺ஋Λߋ৽͢ΔͨΊͷίʔυΛॻ ͍ͨΓ͠ͳ͚Ε͹͍͚ͳ͔ͬͨ΋ͷ Λ؆୯ʹ͔͚ΔΑ͏ʹ͢Δ
  34. react-hooks-testing-library testWidgets('debugFillProperties', (tester) async { await tester.pumpWidget( HookBuilder(builder: (context) {

    usePrevious(42); return const SizedBox(); }), ); await tester.pumpWidget( HookBuilder(builder: (context) { usePrevious(21); return const SizedBox(); }), ); final element = tester.element(find.byType(HookBuilder)); expect( element .toDiagnosticsNode(style: DiagnosticsTreeStyle.offstage) .toStringDeep(), equalsIgnoringHashCodes( 'HookBuilder\n' ' │ usePrevious: 42\n' ' └SizedBox(renderObject: RenderConstrainedBox#00000)\n', ), ); }); ࢀߟίʔυͱͯ͠ Flutter Hooks ͔Β ͖࣋ͬͯͨ usePrevious ͷςετίʔ υ͕ͪ͜ΒʹͳΓ·͢ ᶃ Ͱ 42 Λ usePrevious ʹ౉͢ ᶄ Ͱ 21 Λ usePrevious ʹ౉͢ ᶅ Ͱ usePrevious ͷঢ়ଶΛ֬ೝ͢Δ ᶃ ᶄ ᶅ
  35. react-hooks-testing-library testWidgets('debugFillProperties', (tester) async { final result = await buildHook(

    (value) => usePrevious(value), initialProps: 42, ); await result.rebuild(21); expect(result.current, 42); }); ΋͠Ծʹ Flutter Ͱಉ͡Α͏ͳ͜ͱ͕Ͱ͖ ͨͱͨ͠Βɺ͜Ε͘Β͍ͷίʔυྔͰ͢ react-hooks-testing-library ͸ͦΕ͘Β͍ ϘΠϥʔίʔυΛ࡟ݮͰ͖ΔϥΠϒϥϦ ᶃ ᶄ ᶅ
  36. ·ͱΊ • ΫϥΠΞϯταΠυ͕શͯએݴత UI ͷํ޲ʹͳͬͯ͘Ε͓ͨ ͔͛Ͱ Flutter Ͱ࡞Δʹͯ͠΋ React ͔Βֶ΂ͯ

    Jetpack Compose Ͱ࡞Δʹͯ͠΋ࢀߟʹͳΔ΋ͷ͕ΰϩΰϩͱస ͕͍ͬͯΔ • ֤ݴޠͷҧ͍ͳΜͯࠣࡉͳ΋ͷͰ UI ϑϨʔϜϫʔΫΛͦΕͧ ΕֶͿͷ͕େม͚ͩͬͨͲɺ༏͍͠ੈքʹͳ͖ͬͯͨ