InheritedWidget解説

こんにちはnasustです 。

今回はInheritedWidgetを解説します。

InheritedWidget class - widgets library - Dart API

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;
}
dart

InheritedWidgetのドキュメントにあるサンプルコードをFileに置き換えただけの実装です。

FileInheritedWidgetが作成されると、static FileInheritedWidget of(BuildContext context)を呼んだStatebuildメソッドが実行されます。

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(),
      ),
    );
  }
}
dart

_ClockHomePage

_ClockHomePageStatefulWidgetに変更しました。 壁紙を変更した際に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,
    );
  }
}
dart

InheritedWidgetから画像ファイルを取得するように変更しました。 親のFileInheritedWidgetが作成され、画像ファイルが更新されるとbuild()が実行されて再描画します。

InheritedWidgetのまとめ

サンプルコードでInheritedWidgetの動きが理解できたと思います。 さらに理解できるようにInheritedWidgetの動きを改めてリストアップします。

  1. _ClockHomePageStatebuild()FileInheritedWidgetをツリーに追加する。
  2. _WallpaperWidgetStatebuild()FileInheritedWidget.of()を実行して依存関係を登録する。Fileの値を取得する。以降、親のFileInheritedWidgetが変更されると通知されるようになる。
  3. 画像を選択して再度、_ClockHomePageStatebuild()を実行する。
  4. 親のFileInheritedWidgetの変更が通知されると、_WallpaperWidgetStatebuild()が再実行されて、画像を表示する。

InheritedWidgetの動きを見るとObserverパターンで実装されているのが分かります。 通知とリスナーの登録が自動化されています。

InheritedWidgetを使用することで、直接リスナーを登録しなくても変更を受け取れるので、リファクタリングしやすい実装になりますね。

prevnext