使用 TDD + Clean Architecture 開發 Flutter 專案課程筆記 13 ~ 14

hands typing on a laptop keyboard Flutter
Photo by cottonbro studio on Pexels.com

前言

邏輯、流程的部分都已經處理掉了,接下來應該是把畫面跟定義好的程式串起來就會動了;課程也即將進入尾聲。

筆記

Flutter TDD Clean Architecture Course [13] – Dependency Injection

Flutter TDD Clean Architecture Course [13] – Dependency Injection - Reso Coder
Subscribe Get the f​ull project We have all of the individual pieces of the app architecture in place. Before we can uti...

這集主要是要教學 dependency injection,在 Android 專案我習慣用 Koin 來處理這個需求,因為建構子實在需要太多東西了,假設 10 個地方需要 NumberTriviaBloc,那我就必需手動準備好 30 個參數。有了 DI 可以加速我們開發,只要宣告一次 NumberTriviaBloc,就可以到處使用。

影片開頭有個註解上色的 plugin,JetBrain 版本也有,但格式不太一樣:

Better Comments - IntelliJ IDEs Plugin | Marketplace
The Better Comments plugin will help you create more human-friendly comments in your code. With this plugins, you will b...

作者在註冊 DI 時有提到:因為 Bloc 有 Stream 的關係,不應該使用 Singleton,避免停在 Stream 的中間,導致狀態錯誤。至於其他的都用 LazySingleton,好處是用到時才會初始化 instance。

這集要注意的地方應該就是:SharedPreferences 的 return type 是 Future,所以整個 DI 都要為它修改成 async / await 的形式。

Flutter TDD Clean Architecture Course [14] – User Interface

最後一集,也就是刻畫面了,原以為是單純的一集,但我太天真:作者影片開始沒多久就開了模擬器,展示程式碼修改後的畫面;當我要把 App 灌進手機,卻顯示了下列錯誤:

E/flutter (24786): [ERROR:flutter/lib/ui/ui_dart_state.cc(199)] Unhandled Exception: Null check operator used on a null value
E/flutter (24786): #0      MethodChannel.binaryMessenger (package:flutter/src/services/platform_channel.dart:142:86)
E/flutter (24786): #1      MethodChannel._invokeMethod (package:flutter/src/services/platform_channel.dart:148:36)
E/flutter (24786): #2      MethodChannel.invokeMethod (package:flutter/src/services/platform_channel.dart:331:12)
E/flutter (24786): #3      MethodChannel.invokeMapMethod (package:flutter/src/services/platform_channel.dart:358:49)
E/flutter (24786): #4      MethodChannelSharedPreferencesStore.getAll (package:shared_preferences_platform_interface/method_channel_shared_preferences.dart:54:22)
E/flutter (24786): #5      SharedPreferences._getSharedPreferencesMap (package:shared_preferences/shared_preferences.dart:191:57)

第一個直覺:我的 Dart 版本大於支援 null-safety 的 2.12,會不會是要升級 package 到支援 null-safety 的版本才可以正常運作?所以就拉了一條 branch 去處理升級後的各種 migration。但很不幸地,修到最後會出現 7 個單元測試的 error,而且是某個必定有值的地方變成 null,完全無法理解,後來就放棄了這個修改。

接著朝第一行的錯誤去找,剛好有一篇也有人是炸在 SharedPreferences,就看到:如果要在 main() 處理 await SharedPreferences.getInstance(),必須要先初始化WidgetFlutterBinding 。

Unhandled Exception: Null check operator used on a null value shared preference
so I tried Shared preference to make user still logged in until they log out but when i tried to restart the app this ap...

後來又查到一篇在講解 WidgetFlutterBinding,篇幅較長,我自己的理解是:今天要初始化 SharedPreferences 必須讓它接到雙平台的 native code,而 WidgetFlutterBinding 提供了與雙平台互動的通道,因此會有順序之分,先有通道才能接觸到 native code。

What Does WidgetsFlutterBinding.ensureInitialized() do?
I am trying to use the Firebase package with the below line of code. I really want to know what this line of code actual...

這個問題處理掉後,理所當然地繼續刻畫面,但使用到 bloc 時,出現了下述錯誤:

../../Documents/flutter/.pub-cache/hosted/pub.dartlang.org/provider-3.2.0/lib/src/delegate_widget.dart:194:18: Error: Superclass has no method named 'inheritFromElement'.
    return super.inheritFromElement(ancestor, aspect: aspect);

查了一下,發現是因為我的 Flutter 版本 >= 2.0.0,有個叫做 provider 的最低版本至少要 5.0.0,而此時我的 bloc 依賴的 provider 版本是 3.0.0,只好心一橫,將 bloc 升到最新。接下來就是無止盡地各種 migrate 跟測試 dependencies 版本的相容,最後還是無法修好壞掉的 bloc 單元測試(又回到一開始第一個直覺的輪迴),為了繼續上課,就直接抄別人的 clone 來修改了:

Build software better, together
GitHub is where people build software. More than 100 million people use GitHub to discover, fork, and contribute to over...

因為上述問題我卡了近兩天,之後正式開發一定先把所有 dependencies 升好升滿(雖然根據經驗升到最新也不是一件好事),否則為了版本問題去修一堆東西真的太血尿了。

回到正題,這集大多時間都在刻畫面,其中比較重要的是:利用 BlocProvider 來送出 event 以及用 BlocBuilder 來給予 state,只要這兩個串起來就可以兜起所有東西。作者使用的 RaisedButton 已經 deprecated,所以我改用 ElevatedButton 來取代:

            Expanded(
              // Search concrete button
              child: ElevatedButton(
                child: Text('Random'),
                style: ElevatedButton.styleFrom(
                  primary: Theme.of(context).accentColor,
                ),
                onPressed: dispatchRandom,
              ),
            ),

在完成這個課程後,我還另外補充了 Github Actions 來處理自動單元測試,也用 codecov 補上測試覆蓋率的 badge,但奇怪的是:本地跑測試是 100% 通過,但 CI 會錯兩個,讓人滿困擾的。

專案連結

想看我實作的完整專案程式內容請瀏覽我的 github:

留言列表

タイトルとURLをコピーしました