【Flutter】BuildContext context【よく理解せずに使っていたものたち】
Flutter を触り始めた頃、context
の正体が分からないまま何となく使っていました。
この記事では自分なりに調べて咀嚼した内容をまとめています。
context
とは
まず、どんな使われ方をしているか、例を挙げておさらいします。
// build関数
Widget build(BuildContext context) {
// ...
}
// Theme.of(context)
ThemeData theme = Theme.of(context);
// MediaQuery.of(context)
double screenWidth = MediaQuery.of(context).size.width;
// Navigator.of(context)
Navigator.of(context).push(MaterialPageRoute(builder: (context) => NewPage()));
build
関数はflutter create
で作られる雛形でも登場しますね。
まず言葉のイメージを掴むために、一般的な単語としての意味を確認します。
Weblio辞典曰く、context
とは
(文章の)前後関係、文脈、脈絡、コンテキスト、状況、環境
という意味を持ちます。
では早速、context
を深掘っていきましょう。
まず、context
のデータ型を見てみると、BuildContext
となっています。
いきなり新しい単語が出てきました。
BuildContext
とは
BuildContext
はウィジェットの位置情報を持つオブジェクトで、ツリーを遡って親ウィジェットの情報を取得するのに使われます。
BuildContext
の動作を理解する
具体的にTheme.of(context)
を例に、BuildContext
の挙動を見てみましょう。
まずMaterialApp
のtheme
でデフォルトのprimaryColor
をred
にします。
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primaryColor: Colors.red, // ここ
useMaterial3: true,
),
home: MyPage(),
);
}
}
次にMyPage
というクラスを作成し、画面中央にContainer
を配置し、Theme.of(context).primaryColor
で塗りつぶします。
class MyPage extends StatelessWidget {
const MyPage({super.key});
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
width: 200,
height: 200,
color: Theme.of(context).primaryColor, // red
),
),
);
}
}
この時、Container
はMaterialApp
のtheme
で指定しているred
になります。
では次に、特定のウィジェット内ではTheme.of(context).primaryColor
をblue
にしたいとします。
以下のコードのように、Theme
クラスのThemeData
でprimaryColor
をblue
に指定すれば、その子ウィジェット以下で呼び出すTheme.of(context).primaryColor
はblue
になるでしょうか。
class MyPage extends StatelessWidget {
const MyPage({super.key});
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Theme(
data: ThemeData(primaryColor: Colors.blue),
child: Container(
width: 200,
height: 200,
color: Theme.of(context).primaryColor, // blueではなくred
),
),
),
);
}
}
// 注意:このコードでは色を変更できていません
答えはNOです。
なぜblue
にならないのか、BuildContext
の挙動を段階を追って見ていきます。
-
Container
ウィジェットがTheme.of(context)
を参照します。 -
Theme.of(context)
がcontext.dependOnInheritedWidgetOfExactType<Theme>()
を呼び出します。(dependOnInheritedWidgetOfExactType<T>
はInheritedWidget
を検索し、見つかったらInheritedElement
に依存関係を登録するメソッドです。) -
BuildContext
が現在のウィジェットの位置を特定します。 - ウィジェットツリーを遡り、最も近い
Theme
(InheritedWidget
)を探します。 - 見つかった
Theme
のThemeData
を返します。
BuildContext
がTheme
の外で作られているため、Theme.of(context)
をTheme
内で使っても、外側のTheme
を参照してしまうのです。
InheritedWidget
とは
InheritedWidget
はウィジェットツリー内で子ウィジェットにデータを効率的に共有するための仕組みです。
通常、ウィジェット間でデータを共有する場合、親から子にプロパティを通してデータを渡していきます。しかし、アプリ全体で共有するデータ(例: テーマ、画面サイズ、ナビゲーション情報など)を渡す場合、すべてのウィジェットに手動でプロパティを追加するのは非効率です。
InheritedWidget
は、この問題を解決するために設計されており、親ウィジェットが提供するデータをツリー内の任意の子ウィジェットが直接取得できるようにします。
例えばTheme.of(context)
やMediaQuery.of(context)
は、内部でInheritedWidget
を利用してデータを共有しています。
ではTheme.of(context).primaryColor
をblue
に上書きするにはどうしたらいいでしょう。
BuildContext
が遡った先のTheme
がMyPage
で呼び出したTheme
になるようにすればいいのです。そのためにはTheme
の下でBuildContext
を新しく作成する必要があります。
BuildContext
を新しく作成するには主に2つの方法が挙げられます。
-
Builder
ウィジェットを使用
Builder
ウィジェットを使うことで、新しいBuildContextが作成されます。 -
コンポーネント化してbuildを呼ぶ
ウィジェットをコンポーネント化することで、再度build
関数が呼ばれ、新しいBuildContext
が作成されます。
今回はBuilder
ウィジェットを使用する方法を例に挙げます。
class MyPage extends StatelessWidget {
const MyPage({super.key});
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Theme(
data: ThemeData(primaryColor: Colors.blue),
child: Builder(
builder: (BuildContext context) {
// ↑ 新しく作られたBuildContext
return Container(
width: 200,
height: 200,
color: Theme.of(context).primaryColor, // blue
// ↑ このcontextはBuilderで新しく作られたBuildContext
);
},
),
),
),
);
}
}
これでContainer
の色がblue
になりました!
最後に
これまでBuildContext
について深掘りしてきました。
改めて、この記事で学んだことを振り返ると以下のようになります。
-
BuildContext
はウィジェットの「位置情報」を持ち、それをもとに親ウィジェットから情報を取得する。 -
Theme.of(context)
のような関数は、BuildContext
を基準にツリーを遡って情報を探す。 -
Theme
を上書きするには、新しいBuildContext
を作る必要があり、その方法としてBuilder
やコンポーネント化がある。
BuildContext
を理解することで、テーマやナビゲーションなどの Flutterの仕組みをより直感的に扱えるようになるでしょう。
Discussion