SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

Flutterで始めるモバイルアプリ開発

FlutterのBuildContextとは?──その役割と制限をきちんと理解しよう

Flutterで始めるモバイルアプリ開発 第25回

  • X ポスト
  • このエントリーをはてなブックマークに追加

BuildContextとは

 ここまでのContextに関する理解があれば、BuildContextはFlutter、もしくはUIフレームワークに共通する特徴について理解すれば問題ないはずです。そこで、Flutterでの重要なContextであるBuildContextについて説明します。

画面を構成するWidgetと定義としてのWidgetの関係

 Flutterでは各部品はすべてWidgetというクラスのインスタンスとして実行時に構築されますが、それらのWidgetを作成するためのコードは図3のように再利用されます。

 そして、それらのコードからは実際のインスタンスを意識したコードが記述しにくくなっています。なぜなら、実際に画面上に表示されるWidgetの制御は表示上の最適化のために多くの部分でFlutterのフレーム側が制御しており、プログラマが想像している状況と異なる場合もあるからです。

図3:画面でのWidgetインスタンスツリーとコードの関係図
図3:画面でのWidgetインスタンスツリーとコードの関係図

 しかし、実際には画面表示だけではなく制御にかかわる部分では、このツリー構造やその場所を意識し制御する必要もあります。特にStatefulWidgetの場合には部品の状態とデータなどを保持するState情報なども管理しており、それらのデータや機能に他のWidgetからアクセスできる必要があります。

 そこで、Flutterでは図4のようにそれぞれの部品自体の情報を管理するBuildContextをもち、プログラマはBuildContextを通じて制御を行います。BuildContextはWidgetのbuildメソッド(リスト1)でアクセスできます。

リスト1]Widgetのbuildメソッド
class WidgetA1 extends StatelessWidget { // (1) Widgetクラス
  // 省略
  @override
  Widget build(BuildContext context) { // (2) MyAppのBuildContext
    // 省略
  }
  // 省略
}

 (1)ではWidgetA1という名前でWidgetを定義しています。そして、(2)では部品ツリー上のWidgetA1を識別するBuildContextが引数として渡されます。StatefulWidgetでもほぼ同様であり、Widgetを構築する前にそのWidgetを作成するためのBuildContextが用意されます。

図4:Widgetでのbuildとインスタンスツリーの関係図
図4:Widgetでのbuildとインスタンスツリーの関係図

Elementとは

 BuildContextの説明から少々離れますが、読者がReactやVueJSのような他のUIフレームワークを使ったことがあるのであれば、BuildContextの実装であるElementクラスを理解すると、これまでの経験から理解を助けることもできます。

 例えば、BuildContextの実装は、リスト2のようにElementクラスでもあります。

[リスト2]BuildContextの実装(FlutterSDKのソース:src/widget/framework.dartから抜粋)
abstract class Element extends DiagnosticableTree implements BuildContext {
  Widget get widget;
  // 省略
}

 そして、このElementはWidgetライフサイクルを図5のように管理しています。

図5:Elementが管理するWidgetのライフサイクル
図5:Elementが管理するWidgetのライフサイクル

 ここで示すメソッドや役割などをみると、ReactやVueJSのようなものと似ていると感じる方もいると思います。また他のUIフレームワークを使ったことがない読者の場合にはここまでの理解を思い出していただければ共通する部分があるので役立つはずです。

BuildContextを引数にするofメソッドについて

 Flutterを利用していると、Navigator.ofのようなBuildContextを引数にとるofメソッドをたびたび見かけます。これらのメソッドが何をやっているかをもう少し詳しく見ていきます。

 また、先ほどのより汎用的なContextの図に当てはめてofメソッドを考えると、図6の各Contextで管理している各データやインスタンスの実体にアクセスするためのメソッドと理解することもできます。

図6:抽象的なofメソッドの理解
図6:抽象的なofメソッドの理解

ofメソッドの仕組み

 BuildContextを引数にするofメソッドには、リスト3のようにいろいろあります。

[リスト3]BuildContextの引数をとるofメソッドの一例
MediaQuery.of(context)
Navigator.of(context)
ScaffoldMessenger.of(context)

 いずれのメソッドを使う場合でも、エラーなく動作している場合にはあまり意味を意識していなくても問題になることはありません。それぞれのofメソッドでやっていることは単純で、これまでのBuildContextのツリー構造がイメージできていれば理解は簡単です。

 これは図7のように自分自身のBuildContextから親からその親へと探索し、対象のStateオブジェクトが見つかるとそのオブジェクトを返します。

図7:ofメソッドが探索する順番と制限
図7:ofメソッドが探索する順番と制限

 このofメソッドで注意すべきなのは、引数で指定したBuildContextの親から探索するという点です。例えば、間違えたBuildContextを指定した場合には、リスト4のようなエラーが生じます。その場合には、この制限を思い出してもらえれば問題を解決できるはずです。

[リスト4]正しくないBuildContextを選択した場合のエラー例
======== Exception caught by gesture ===============================================================
The following assertion was thrown while handling a gesture:
Scaffold.of() called with a context that does not contain a Scaffold.

利用したいBuildContextを用意する

 ofメソッドを使いたいときに、引数として利用したいBuildContextがコード上存在しない場合があります。または、もっとも近くに存在するBuildContextを使えればよいと理解している方もいるかもしれません。

 しかし、リスト5のようなコードを実行した際には、必要なScaffoldのステート(ScaffoldState)が見つからず先ほどのようなエラーが生じます。

[リスト5]目的のオブジェクトが見つからないコード例
Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
            title: Text("タイトル"),
            leading: IconButton(
                icon: Icon(Icons.menu),
                onPressed: (){
                    Scaffold.of(context).openDrawer(); // (1) 正常に動作しないコード
                }
            ),
            // 省略
        ))}

 (1)のコード自体には記述上の問題はありませんが、引数として利用しているcontextが正しくなくエラーが発生します。

 なぜならこのコードの場合には、図8のような探索を行います。従って、このような場合にはScaffoldとIconButtonのWidgetの間に存在するBuildContextを作る必要があります。

図8:問題となるofメソッドの利用例
図8:問題となるofメソッドの利用例

 そのような場合に利用するのがBuilderウィジェットで、問題ないように変更したコードがリスト6です。

[リスト6]目的のオブジェクトが見つかるようにBuilderを使ったコード例
Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
            title: Text("タイトル"),
            leading: Builder(
                builder: (context) => IconButton(  // (1) 必要なBuildContextを用意する
                    icon: Icon(Icons.menu),
                    onPressed: (){
                        Scaffold.of(context).openDrawer(); // (2) 正常に動作するコード
                    }
                )
            ),
            // 省略
        ))}

 (1)のbuilderプロパティの指定部分で、AppBarウィジェットとIconButtonウィジェットの間にBuildContextが存在するようにします。

 そのため、先ほどとコードは同じにもかかわらず(2)のコードは問題なく動作するようになります。この動作を先ほどと同じような図で示したのが、図9です。

図9:Builderを使ったの利用例
図9:Builderを使ったの利用例

 Builderウィジェットを使わなくても、必要なBuildContextをコード上に存在させたいだけなので、自分で定義したStatefulWidgetやStatelessWidgetではこのようなBuilderウィジェットは必要ありません。しかし、BuildContextを使いたいだけの場合が多いので、このようにBuilderウィジェットが多用されます。

次のページ
Keyについて

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
Flutterで始めるモバイルアプリ開発連載記事一覧

もっと読む

この記事の著者

WINGSプロジェクト 小林 昌弘(コバヤシ マサヒロ)

WINGSプロジェクトについて>有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

山田 祥寛(ヤマダ ヨシヒロ)

静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for Visual Studio and Development Technologies。執筆コミュニティ「WINGSプロジェクト」代表。主な著書に「独習シリーズ(Java・C#・Python・PHP・Ruby・JSP&サーブレットなど)」「速習シリーズ(ASP.NET Core・Vue.js・React・TypeScript・ECMAScript、Laravelなど)」「改訂3版JavaScript本格入門」「これからはじめるReact実践入門」「はじめてのAndroidアプリ開発 Kotlin編 」他、著書多数

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/18583 2023/11/13 11:00

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング