InheritedWidgetを解説
こんにちはnasustです 。
今回はInheritedWidget
を解説します。
InheritedWidget
は変数の値などをツリーあるWidgetに効率的に伝達するクラスです。
このWidgetを使用するとGlobalKey
を使用しなくても対象のWidgetに変数の値を伝達できます。
解説用のソースは「シンプルな壁紙対応時計を開発する」を使用します。これをGlobalKey
からInheritedWidget
に変更して解説します。
InheritedWidget
対応のソースは以下のリンクにあります。
file_inherited_widget.dart
libs/widgets/file_inherited_widget.dartは以下の通りです。 InheritedWidgetを継承しています。
import 'dart:io';
import 'package:flutter/widgets.dart';
class FileInheritedWidget extends InheritedWidget {
const FileInheritedWidget({
Key key,
this.file,
Widget child,
}) : super(key: key, child: child);
final File file;
static FileInheritedWidget of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<FileInheritedWidget>();
}
bool updateShouldNotify(FileInheritedWidget old) => file != old.file;
}
InheritedWidget
のドキュメントにあるサンプルコードをFile
に置き換えただけの実装です。
FileInheritedWidget
が作成されると、static FileInheritedWidget of(BuildContext context)
を呼んだState
のbuild
メソッドが実行されます。
build
メソッドが実行される際に、context.dependOnInheritedWidgetOfExactType
が返すFileInheritedWidget
から最新の画像ファイルが取得できます。
see also: dependOnInheritedWidgetOfExactType method - BuildContext class - widgets library - Dart API
main.dart
lib/main.dartをInheritedWidget
で壁紙を変更するように変更しました。
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'widgets/clock_widget.dart';
import 'widgets/file_inherited_widget.dart';
import 'widgets/wallpaper_widget.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Clock',
theme: ThemeData.dark(),
home: _ClockHomePage(),
);
}
}
class _ClockHomePage extends StatefulWidget {
State<StatefulWidget> createState() {
return _ClockHomePageState();
}
}
class _ClockHomePageState extends State<_ClockHomePage> {
File _imageFile;
_pickImage() async {
var imageFile = await ImagePicker.pickImage(source: ImageSource.gallery);
setState(() {
_imageFile = imageFile;
});
}
Widget build(BuildContext context) {
return Scaffold(
body: GestureDetector(
behavior: HitTestBehavior.opaque,
child: Stack(
children: <Widget>[
Container(
child: FileInheritedWidget(
child: WallpaperWidget(),
file: _imageFile,
),
),
Container(
child: Center(
child: ClockWidget(),
),
),
],
),
onTap: () => _pickImage(),
),
);
}
}
_ClockHomePage
_ClockHomePage
はStatefulWidget
に変更しました。
壁紙を変更した際にInheritedWidget
を作成する為に変更しました。
_ClockHomePageState
_pickImage()
とbuild()
を_ClockHomePageState
に移動して、
GlobalKey
からInheritedWidget
を使用するように変更しました。
FileInheritedWidget
で指定した画像ファイルが変更されると、
WallpaperWidget
が再描画されます。
wallpaper_widget.dart
import 'package:flutter/widgets.dart';
import 'file_inherited_widget.dart';
class WallpaperWidget extends StatefulWidget {
WallpaperWidget({Key key}) : super(key: key);
State<WallpaperWidget> createState() {
return _WallpaperWidgetState();
}
}
class _WallpaperWidgetState extends State<WallpaperWidget> {
Widget build(BuildContext context) {
var imageFile = FileInheritedWidget.of(context).file;
Image image;
if (imageFile != null) {
image = Image.file(imageFile, fit: BoxFit.cover);
}
return Container(
constraints: BoxConstraints.expand(),
child: image,
);
}
}
InheritedWidget
から画像ファイルを取得するように変更しました。
親のFileInheritedWidget
が作成され、画像ファイルが更新されると
build()
が実行されて再描画します。
InheritedWidgetのまとめ
サンプルコードでInheritedWidget
の動きが理解できたと思います。
さらに理解できるようにInheritedWidget
の動きを改めてリストアップします。
_ClockHomePageState
のbuild()
でFileInheritedWidget
をツリーに追加する。_WallpaperWidgetState
のbuild()
でFileInheritedWidget.of()
を実行して依存関係を登録する。File
の値を取得する。以降、親のFileInheritedWidget
が変更されると通知されるようになる。- 画像を選択して再度、
_ClockHomePageState
のbuild()
を実行する。 - 親の
FileInheritedWidget
の変更が通知されると、_WallpaperWidgetState
のbuild()
が再実行されて、画像を表示する。
InheritedWidget
の動きを見るとObserverパターンで実装されているのが分かります。
通知とリスナーの登録が自動化されています。
InheritedWidget
を使用することで、直接リスナーを登録しなくても変更を受け取れるので、リファクタリングしやすい実装になりますね。