📦

【Flutter】PackageInfoをPackageInfo.of(context)で呼べるようにする

2024/03/07に公開

MediaQuery.of(context)と同じようにpackage_info_plusPackageInfo.of(context)で呼び出せるようにする方法です。

PackageInfoWidgetの作成

import 'package:flutter/material.dart';
import 'package:package_info_plus/package_info_plus.dart' as p;

class PackageInfoWidget extends StatefulWidget implements ProxyWidget {
  
  final Widget child;

  const PackageInfoWidget({super.key, required this.child});

  
  PackageInfoWidgetState createState() => PackageInfoWidgetState();
}

class PackageInfoWidgetState extends State<PackageInfoWidget> {
  p.PackageInfo? _info;

  
  void initState() {
    p.PackageInfo.fromPlatform().then((value) {
      _info = value;
    });
    super.initState();
  }

  
  Widget build(BuildContext context) =>
      PackageInfo(() => _info, child: widget.child);
}

class PackageInfo extends InheritedWidget {
  final p.PackageInfo? Function() _getInfo;

  const PackageInfo(this._getInfo, {super.key, required super.child});

  static p.PackageInfo? maybeOf(BuildContext context) =>
      context.dependOnInheritedWidgetOfExactType<PackageInfo>()?._getInfo();

  static p.PackageInfo of(BuildContext context) {
    final result = maybeOf(context);
    assert(result != null, 'No PackageInfo found in context');
    return result!;
  }

  
  bool updateShouldNotify(_) => false;
}

of(context)で呼び出せるようにするにはInheritedWidgetが必要なのですが、InheritedWidgetだけだとこのWidgetが再ビルドされた時に_infoが初期化されてしまう可能性があるため、StatefulWidgetを親に配置しています。

本来は保持している値(今回は_info)に変化があった場合、updateShouldNotifytrueを返す必要がありますが、p.PackageInfoは一度生成した後は変化することがないので常にfalseを返しています。

また、_infoInheritedWidgetの引数として渡し、初期化が終わった時にsetStateで一度ビルドし直す方法がFlutterの設計としては正しいのですが、無駄に重くなるので_getInfo()で呼び出す方式にしています。

実際に利用する

特に理由がなければrunAppの真下に配置すると楽です。

runApp(PackageInfoWidget(child: MyApp()));

これでbuild(BuildContext context)内で呼び出せるようになります。

print(PackageInfo.of(context).buildNumber);

基本的に呼び出す頃には初期化が終わっていると思いますが、アプリを起動してすぐに利用することがあるなど、PackageInfoの初期化が間に合わない可能性のある場合はmaybeOfを使用してください。

余談

Flutterは、このようにInheritedWidgetに流したいModelクラスを持たせる方法が一応公式の書き方です(参考)。
これでは必要なModelクラスの数だけInheritedWidgetを作らなければなりません。超面倒

恐らく公式としては「誰かもっと便利にInheritedWidgetを使えるパッケージ作ってな!」という考えのもと、この書き方だけしか作らなかったのだと思いますが、
それに呼応するように Remi Rousselet 氏が「InheritedWidgetをもっと使いやすく、再利用性を高めるためのラッパー[1]」としてproviderを作り、単なるラッパーでは限界を感じたのか更なる利便性を求めてriverpodを作りました。

Remi氏凄すぎ。

備考

本記事は以下の環境で検証しました

Flutter 3.19.2 • channel stable • https://github.com/flutter/flutter.git
Framework • revision 7482962148 (9 days ago) • 2024-02-27 16:51:22 -0500
Engine • revision 04817c99c9
Tools • Dart 3.3.0 • DevTools 2.31.1
脚注
  1. providerの冒頭にある"A wrapper around InheritedWidget to make them easier to use and more reusable."の訳 ↩︎

GitHubで編集を提案
合同会社zoome(ズーム)

Discussion