概要
やりたいこと
flutter create 時に作成されるカウンターアプリを Class と Widget 分けていきます。
下記を参考にさせていただきました。追加で NullSafety などを対応していく。
Flutter 初期アプリ自体の解説はこのブログが神。
環境
Mac Big Surに構築。
$ flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 2.8.1, on macOS 11.4 20F71 darwin-x64, locale ja-JP)
[✓] Android toolchain - develop for Android devices (Android SDK version 30.0.3)
[✓] Xcode - develop for iOS and macOS (Xcode 13.2.1)
[✓] Chrome - develop for the web
[✓] Android Studio (version 4.2)
[✓] VS Code (version 1.63.2)
[✓] Connected device (2 available)
Widget, Class, ファイルを分割
初期
以下が flutter create 時の lib/main.dart。これを改造する。
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
body を分ける
_MyHomePageState が大きい。body を別 Class にする。
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: MyHomePageBody(counter: this._counter),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
class MyHomePageBody extends StatelessWidget {
MyHomePageBody({Key? key, this.counter}) : super(key: key);
final int? counter;
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text('You have pushed the button this many times:'),
Text('$counter', style: Theme.of(context).textTheme.headline4),
],
),
);
}
}
floatingActionButton を分ける
まだ _MyHomePageState 大きいです。 floatingActionButton も別 Class にしましょう。
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: MyHomePageBody(counter: this._counter),
floatingActionButton: MyHomePageFab(incrementCounter: this._incrementCounter), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
class MyHomePageFab extends StatelessWidget {
MyHomePageFab({Key? key, this.incrementCounter}) : super(key: key);
final VoidCallback? incrementCounter;
@override
Widget build(BuildContext context) {
return FloatingActionButton(
onPressed: this.incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
);
}
}
ファイルを分ける
分け方(ディレクトリ構成、デザインパターン)は様々。今回は単純に class ごとに分ける。
分ける前。
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: MyHomePageBody(counter: this._counter),
floatingActionButton: MyHomePageFab(incrementCounter: this._incrementCounter), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
class MyHomePageBody extends StatelessWidget {
MyHomePageBody({Key? key, this.counter}) : super(key: key);
final int? counter;
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text('You have pushed the button this many times:'),
Text('$counter', style: Theme.of(context).textTheme.headline4),
],
),
);
}
}
class MyHomePageFab extends StatelessWidget {
MyHomePageFab({Key? key, this.incrementCounter}) : super(key: key);
final VoidCallback? incrementCounter;
@override
Widget build(BuildContext context) {
return FloatingActionButton(
onPressed: this.incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
);
}
}
作成した2つの class ごとに、ファイルを分ける。
- libs/MyHomePageBody.dart
- libs/MyHomePageFab.dart
libs/MyHomePageBody.dart
import 'package:flutter/material.dart';
class MyHomePageBody extends StatelessWidget {
// 略
}
libs/MyHomePageFab.dart
import 'package:flutter/material.dart';
class MyHomePageFab extends StatelessWidget {
// 略
}
main.dart でファイルをインポートする。
import 'MyHomePageBody.dart';
import 'MyHomePageFab.dart';
main.dart と app.dart に分ける
main.dart。ここでは最低限のことしかしません。
import 'package:flutter/material.dart';
import 'app.dart';
void main() {
runApp(const MyApp());
}
app.dart。アプリの基盤となる部分を定義。
- MaterialApp の宣言
- 状態の管理(いずれ別に)
import 'package:flutter/material.dart';
import 'MyHomePageBody.dart';
import 'MyHomePageFab.dart';
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: MyHomePageBody(counter: this._counter),
floatingActionButton: MyHomePageFab(incrementCounter: this._incrementCounter), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
test/widget_test.dart で MyApp を呼び出しているため、import 先を変更。
//import 'package:hello/main.dart';
import 'package:hello/app.dart';
これでエラーなく起動されます。
まとめ
コードは Github にまとめてあります。
エラー
The parameter ‘counter’ can’t have a value of ‘null’ because of its type, but the implicit default value is ‘null’. (Documentation)
パラメータ「counter」は、そのタイプのために「null」の値を持つことはできませんが、暗黙のデフォルト値は「null」です。 (ドキュメンテーション)
元の記述。NullSafety な記述。
MyHomePageBody({Key? key, this.counter}) : super(key: key);
final int counter;
null を許容。
MyHomePageBody({Key? key, this.counter}) : super(key: key);
final int? counter;
The argument type ‘Function?’ can’t be assigned to the parameter type ‘void Function()?’.
引数の型「関数?」パラメータタイプ 'void Function()?'に割り当てることはできません。
こう書いてた。
MyHomePageFab({Key? key, this.incrementCounter}) : super(key: key);
final Function? incrementCounter;
VoidCallback に変更。
MyHomePageFab({Key? key, this.incrementCounter}) : super(key: key);
final VoidCallback? incrementCounter;
コメント