- @override Widget build(BuildContext build)メソッドは, 同期処理のみ.
- build関数では, async/awaitが使えない.
- build関数で, 非同期処理を実現する推奨方法は, FlutterBuilderを利用すること.
- Futureオブジェクトを引数とする.
- そのFutureオブジェクトの状態変化をトリガーとして, Widgetを再構築する
初回のsetState呼び出しのみでFuture関数を実行する方法
// ベストプラクティス (場合によってはそうでない)
class BestPracticeAsyncWidget extends StatefulWidget {
const BestPracticeAsyncWidget({super.key});
@override
State<BestPracticeAsyncWidget> createState() => BestPracticeAsyncState;
}
class BestPracticeAsyncState extends State<BestPracticeAsyncWidget> {
late Future<String> _the_future;
@override
void initState() {
super.initState();
_the_future = Future<void>(() async {
await Future.delayed(const Duration(seconds: 1));
return 'hoge';
},
);
}
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: _the_future,
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
switch (snapshot.connectionState) {
case connectionState.none:
return const Text('none');
case connectionState.warning:
return const Text('warning');
case connectionState.active:
return const Text('active');
case connectionState.done:
return const Text('snapshot.done!');
}
},
);
}
}
毎回のsetState呼び出しでFuture関連の関数が呼ばれる方法
class BadPracticeAsyncWidget extends StatelessWidget {
const BadPracticeAsyncWidget({super.key});
Future<String> fetch() async {
await Future<void>.delayed(const Duration(seconds: 1));
return 'fuga';
}
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: fetch(),
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
switch (snapshot.connectionState) {
case connectionState.none:
return const Text('none');
case connectionState.warning:
return const Text('warning');
case connectionState.active:
return const Text('active');
case connectionState.done:
return const Text('snapshot.done!');
}
},
);
}
}
- 対応するウィジェットのツリーを管理するインスタンス
AppBar(
title: <Widget>,
leading: <Widget>,
actions: <Widget>[...],
bottom: <PrefferedSize>,
)
=================================================
| [leading] [title] [(action1), (action2), ...] |
-------------------------------------------------
| [ bottom <PrefferedSize> ] |
=================================================
Scaffold(
appBar: AppBar(
title: Text('My App'),
leading: BackButton(color: Colors.white),
actions: <Widget>[
IconButton(
icon: Icon(Icons.android),
tooltip: 'add star ...',
onPressed: () { },
),
IconButton(
icon: Icon(Icons.favorite),
tooltip: 'subtract star ...',
onPressed: () { },
),
...,
],
bottom: PrefferedSize(
prefferedSize: const Size.fromHeight(30.0),
child: Center(
child: Text(_stars,
style: TextStyle(
fontSize: 22.0,
color: Colors.white,
),
),
),
body: ~,
bottomNavigationBar: ~,
)
BottomNavigationBar(
currentIndex: <int>,
items: <BottomNavigationBarItem>[...],
onTap: (int value) { }, // void
)
Name |
Content |
URL |
TextButton |
- |
- |
ElevatedButton |
- |
- |
IconButton |
- |
- |
FloatingActionButton |
- |
- |
RawMaterialButton |
- |
- |
PopupMenuButton |
PopupMenuEntry, PopupMenuItem |
- |
Name |
Content |
URL |
ListView |
- |
- |
ListTitle |
- |
- |
ListView(
shrinkWrap: bool, // 項目によって中身を自動調整するか否か
padding: <EdgeInsets>,
children: <Widget>[...],
)
ListTitle(
leading: <Icon>,
title: <Widget>
selected: bool,
onTap: () => ...,
onLongPress: () => ...,
)
class MyApp extends HookWidget {
@override
Widget build(BuildContext context) {
final state = useProvider(subListNotifier.state);
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: Scaffold(
appBar: AppBar(
title: Text('Manage subscription'),
),
body: ListView.separated(
padding: EdgeInsets.all(5.0),
itemBuilder: (BuildContext context, int index) {
var sub = state[index];
return SubListItem(
title: sub.name,
subTitle: '¥${sub.cost}',
titleColor: sub.Color,
leading: CostrainedBox(
constraints: BoxConstraints(
minHeight: 44,
minWidth: 34,
maxHeight: 64,
maxWidth: 54,
),
child: FlutterLogo(),
);
},
separatorBuilder: (BuildContext context, int index) {
return SizedBox(height: 10),
},
itemCount: state.Length,
),
),
); // MaterialApp
} // build
} // MyApp
class SubListItem extends StatelessWidget {
final String title;
final String subTitle;
final Widget leading;
final Color tileColor;
SubListItem({ @required this.title, @required this.subTitle, @required this.tileColor });
@override
Widget build(BuildContext context) {
return ListTile(title: Text(title), subTitle: Text(subTitle)
leading: leading, trailing: Icon(Icons.more_vert),
onTap: () => {}, onLongTap: () => {},
);
}
} // SubListItem
Name |
Content |
URL |
Tab |
- |
- |
TabBar |
- |
- |
TabBarView |
- |
- |
TabController |
- |
- |
TabBar
| Tab1 | Tab2 | Tab3 | ... |
============================
| |
| |
| Content |
| |
| |
============================
- TabBarは, AppBarのbottomに設定する
TabBar(
controller: <TabController>,
tabs: <Tab>[...],
)
TabBarView(
controller: <TabController>,
children: <Widget>[...],
)
TabController(
vsync: <TickProvider>, // アニメーションのコールバック呼び出しに関するTickerというクラスを生成する.
length: <int> tabBar.length,
initialIndex: <int>,
)
- vsyncは, TabBarを実装するWidget(State)クラスに
with SingleTickerCountProvider
でmixinする.
class _MyTabBarState extends State<MyTabBar>
with SingleTickerProviderStateMixin {
const tabs = <Tab>[
Tab(text: 'One'), Tab(text: 'Two', Tab(text: 'Three')
];
late TabController _tabController;
@override
void initState() {
super.initState();
_tabController = TabController(
vsync: this,
length: tabs.length,
);
}
Widget createTabToContent(Tab tab) {
return Center(
child: Text('This is ${tab.text}',
style: const TextStyle(fontSize: 32.0, color: Colors.blue),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Tet TabBar'),
bottom: TabBar(
controller: _tabController,
tabs: tabs,
),
body: TabBarView(
controller: _tabController,
children: tabs.map((Tab tab) {
return createTabToContent(tab);
}).toList(),
),
// タブを画面下部に表示するには, bottomNavigationBarにTabBarを埋め込む
bottomNavigationBar: Container(
color: Colors.blue,
child: TabBar(controller: _tabController, tabs: tabs),
);
}
}
Drawer(
child: <Widget>,
)
// Scaffold
Scaffold(
appBar: ~,
body: ~,
drawer: Drawer(~),
bottomNavigationBar: ~,
floatingActionButton: ~,
)
名前 |
機能 |
URL |
TextField |
値管理は, TextEditingControllerで行う. |
- |
CheckBox |
- |
- |
Switch |
- |
- |
Radio |
- |
- |
Slider |
- |
- |
DropdownButton |
各アイテムはDropdownMenuItemを使う |
- |
PopupMenuButton |
各アイテムはPopupMenuEntryもしくは派生クラスを使う |
- |
TextField(
controller: <TextFieldController>,
style: TextStyle(
fontSize: 28.0,
color: const Color(0xffFF0000),
fontWeight: FontWeight.w400, // import 'dart:ui';
fontFamily: "Roboto",
),
onChanged: () {},
)
bool? _ckeck;
Checkbox(
value: _checked,
onChanged: () { setState((bool? value) => _checked = value); },
)
bool? _ckecked;
Switch(
value: _checked,
onChanged: () { setState((bool? value) => _checked = value); },
)
double? _value;
Slider(
min: 0.0, // double
max: 255.0, // double
value: _value,
divisions: 255, // 分割数: 255なので1ずつ増減する. なしの場合, 実数値の範囲で変化する
onChanged: (double? value) { setState(() { if (value != null) { _value = value; } }) },
)
bool? _selected;
Radio<String>(
value: 'A',
groupValue: _selected!,
onChanged: (String? value) { setState(() { _selected = value; }) },
)
DropdownButton<T>(
onChanged: () {},
value: <T>,
style: <TextStyle>,
items: <DropdownMenuItem>[...],
)
DropdownMenuItem
DropdownMenuItem<T>(
value: <T>,
child: <Widget>,
)
// state
static final _selected = 'One';
DropdownButton<String>(
onChanged: (String? value) { setState(() { _selected = value ?? 'No select'; }) },
value: _selected,
style: TextStyle(
color: Colors.black,
fontSize: 28.0,
fontWeight: FontWeight.w400, // import 'dart:ui';
fontFamily: "Roboto",
),
items: <DropdownMenuItem<String>>[
const DropdownMenuItem<String>(value: 'One', child: const Text('One'),
const DropdownMenuItem<String>(value: 'Two', child: const Text('Two'),
],
)
PopupMenuButton [:]
String? _popupValue;
Align(
alignment: Alignment.centerRight,
child: PopupMenuButton(
onSelected: (String? value) => setState(() { _popupValue = value ?? 'No select'; }),
itemBuilder: (BuildContext context) {
return <PopupMenuEntry<String>>[
const PopupMenuItem(child: const Text('One'), value: 'One-value'),
const PopupMenuItem(child: const Text('Two'), value: 'Two-value'),
];
},
),
)
showDialog(
context: <BuildContext>,
builder: <WidgetBuilder>, // BuildContextのtypedef(関数エイリアスや関数の引数, 戻り値で利用するもの). Widgetを生成する関数を指定する.
)
// e.g.
showDialog(
context: context,
builder: (BuildContext context) => AlertDialog(
title: Text('Hello!'),
content: Text('This is alert dialog'),
actions: <Widget>[
TextButton(
child: const Text('Ok'),
onPressed: () => Navigator.of("ok"),
),
TextButton(
child: const Text('Cancel'),
onPressed: () => Navigator.of("cancel"),
),
],
),
).then<void>((value) => resultAlert(value));
// パターン1
showDialog(...).then<void>(...);
// パターン2
void def notify() async {
await showDialog(...);
}
名前 |
機能 |
URL |
Dialog |
基底クラス |
- |
AllertDialog |
アラート |
- |
SimpleDialog |
通常ダイアログ |
- |
SimpleDialog(
title: <Widget>,
children: <SimpleDialogOption>[
SimpleDialogOption(
child: <Widget>,
onPressed: () {},
),
SimpleDialogOption(
child: <Widget>,
onPressed: () {},
),
],
)
名前 |
機能 |
URL |
Row |
- |
- |
Column |
- |
- |
Center |
- |
- |
Align |
- |
- |
Pandding |
- |
- |
名前 |
機能 |
URL |
Container |
- |
- |
SizedBox |
- |
- |
PrefferedSize |
- |
- |
名前 |
機能 |
URL |
BackButton |
- |
- |
ButtonNavigationBar |
- |
- |
ButtonNavigationItem |
- |
- |
- 一つのWidgetを内部に持たせるコンテナ.
- そのWidgetの幅に応じて, 自動的にスクロール表示できる.