エキサイトの武藤です。
Flutterで多言語対応をする際、基本的にはWidget内でBuildContextからAppLocalizationsを呼び出して利用します。
しかし、複雑なロジックが必要な表示テキストの場合、Viewでそのロジックを書いてしまうとViewのコードが肥大化してしまいます。
その場合、View以外に表示テキストの生成処理を行うようになると思います。
今回は、RiverpodのProviderで表示テキストの生成を行う方法について説明します。
実行環境
以下、実行環境です。
- flutter: 3.3.8
- dart : 2.18.4
pubspec.yml
dependencies: flutter: sdk: flutter flutter_localizations: sdk: flutter intl: "^0.17.0" hooks_riverpod: ^2.1.1 cupertino_icons: ^1.0.2 dev_dependencies: flutter_test: sdk: flutter build_runner: ^2.3.2 flutter_hooks: ^0.18.5+1 flutter_lints: ^2.0.0 flutter: uses-material-design: true generate: true
l10nを使ったテキストの表示
まずはシンプルな実装例です。
今回は日本語のみの対応とします。
app_ja.arbです。
{ "@@locale": "ja", "sampleText": "サンプルテキストです。" }
Viewのコードです。
class MyHomePage extends StatelessWidget { const MyHomePage({super.key}); @override Widget build(BuildContext context) { final appLocalizations = AppLocalizations.of(context)!; return Scaffold( appBar: AppBar( title: const Text('Flutter Demo Home Page'), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text(appLocalizations.sampleText), ], ), ), ); } }
appLocalizations.sampleText
で該当のテキストを取得しています。
ロジックを含む表示テキスト
次に、ロジックを含むのテキスト表示です。
app_ja.arbです。
{ "@@locale": "ja", "taskTitle": "タスク名 : {title}", "@taskTitle": { "placeholders": { "title": { "type": "String" } } }, "taskTitleWithMemo": "タスク名 : {title} メモ : {memo}", "@taskTitleWithMemo": { "placeholders": { "title": { "type": "String" }, "memo": { "type": "String" } } } }
簡単な例ですが、memo
データのありなしで分岐することとします。
次に、ViewとProviderのコードです。
class MyHomePage extends ConsumerWidget { const MyHomePage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { return Scaffold( appBar: AppBar( title: const Text('Flutter Demo Home Page'), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text(ref.watch(taskProvider(TaskProviderParam( title: 'ゴミ捨て', memo: 'ペットボトル、缶', context: context, )))), ], ), ), ); } }
class TaskProviderParam { TaskProviderParam({ required this.title, required this.memo, required this.context, }); final String title; final String memo; final BuildContext context; }
final taskProvider = Provider.family<String, TaskProviderParam>((ref, param) { final context = param.context; final appLocalizations = AppLocalizations.of(context)!; if (param.title.isNotEmpty && param.memo.isNotEmpty) { return appLocalizations.taskTitleWithMemo(param.title, param.memo); } if (param.title.isNotEmpty) { return appLocalizations.taskTitle(param.title); } return ''; });
ロジックはviewに書くのを避けて、Providerに記述しました。
Providerには、表示の判定に使うデータとAppLocalizationsを生成するためのBuildContextを渡す必要があります。 Providerのfamily修飾子では引数を1つしか渡せないので、それらをまとめたParamクラスを作成しました。
viewでProviderを呼び出して、表示テキストを取得します。
l10nを取得するBuildContext を扱うユニットテスト
前項では、Providerにl10nを扱う実装を紹介しました。
今度は、そのProviderのユニットテストを書いてみます。
Future<BuildContext> getContext(WidgetTester tester) async { late BuildContext result; await tester.pumpWidget( MaterialApp( localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: AppLocalizations.supportedLocales, home: Material( child: Builder( builder: (BuildContext context) { result = context; return Container(); }, ), ), ), ); return result; } void main() { final container = ProviderContainer(); testWidgets('タスク名を表示', (WidgetTester tester) async { await tester.runAsync(() async { await getContext(tester).then((value) { final param = TaskProviderParam( title: "ゴミ捨て", memo: "", context: value ); expect( container.read(taskProvider(param)), "タスク名 : ゴミ捨て", ); }); }); }); testWidgets('メモ付きのタスク名を表示', (WidgetTester tester) async { await tester.runAsync(() async { await getContext(tester).then((value) { final param = TaskProviderParam( title: "ゴミ捨て", memo: "ペットボトル、缶", context: value ); expect( container.read(taskProvider(param)), "タスク名 : ゴミ捨て メモ : ペットボトル、缶", ); }); }); }); }
l10nの設定を取得するために、context取得をメソッドに切り出しています。
実際のtest実行時には、testWidget()のWidgetTesterをgetContext()メソッドに渡すことで、l10nを設定されたcontextを取り出しています。
まとめ
Flutterにて、l10nの操作を含むロジックをProviderで行ってみました。
Viewが煩雑にならないために、ある程度複雑度が高くなった場合には有用かと思います。
どなたかの参考になれば幸いです。