【Flutter】設定機能を構築してみる
まえがき
いきなり入力機能追加に伴い、はじめて設定機能を構築しました。
設定の保存にはshared_preferences
が便利なのは知ってたけど使い方がわからない。
そこでいまはやりのChatGPTに聞いてみました。
とりあえずダイレクトに聞いたら、なかなかいい答えが返ってきた。
だけどprovider
を使って状態管理してるからStatefulWidget
じゃ使えないから聞き直し。
質問の仕方が悪くてごめんね。
そうしたらほぼ求めてる答えが返ってきました。
あとはジChatGPTが書いてくれたコードをコピペしてっと。
コードにちょっと手を加えたらあっさり完成しました。
そうだ、これを記事にしよう!!
と思ったらChatGPTに聞いた履歴を消してしまってた・・・。
せっかくのChatGPTネタだったのにぃ〜。
はい!というわけで、起動時に表示する画面を切り替える設定を構築します。
具体的にはページAとページBと設定画面を用意して設定画面で起動時の画面をAとBで切り替えできるようにします。
それでは始めましょう。
プロジェクトを作成
まずはプロジェクト名settings_sample
で新規プロジェクトを作成します。
以下の記事を参考にしてプロジェクト名をsettings_sample
に置き換えて作成してください。
またプロジェクトのテンプレートを「Application(empty)」を選択すると最小限のコードが生成されるのでこちらを選びます。
パッケージをインストール
今回は設定の保存に使うshared_preferences
と状態管理のprovider
をpubspec.yaml
に追加します。
(https://pub.dev/packages/shared_preferences/install)
(https://pub.dev/packages/provider/install)
ターミナルから以下のコマンドを実行します。
flutter pub add shared_preferences
flutter pub add provider
またはpubspec.yaml
のdependencies
のところにshared_preferences: ^2.0.18
とprovider: ^6.0.5
を入力してターミナルでflutter pub get
を実行します。
dependencies:
shared_preferences: ^2.0.18
provider: ^6.0.5
ファイル構成
-
lib
-
view
page_a.dart
page_b.dart
settings.dart
-
viewmodel
setting_model.dart
main.dart
-
lib
ディレクトリ内にview
ディレクトリとviewmodel
ディレクトリを作成します。
view
ディレクトリ内には各画面を作成します。
viewmodel
ディレクトリ内にはSettingModel
を作成します。
view
ディレクトリ内のファイルを作成
ページAとページB
ページAをファイル名page_a.dart
で作成、ページBをファイル名page_b.dart
で作成します。
import "package:flutter/material.dart";
class PageA extends StatelessWidget {
const PageA({super.key});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
onPressed: () {},
icon: const Icon(
Icons.settings,
),
),
),
body: const Center(
child: Text(
"PageA",
style: TextStyle(
fontSize: 60,
color: Colors.red,
),
),
),
);
}
}
import "package:flutter/material.dart";
class PageB extends StatelessWidget {
const PageB({super.key});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
onPressed: () {},
icon: const Icon(
Icons.settings,
),
),
),
body: const Center(
child: Text(
"PageB",
style: TextStyle(fontSize: 60),
),
),
);
}
}
ページA、Bと両方のアプリケーションバーと画面中央に"PageA"
、"PageB"
を表示します。
まずはAppBar
ウィジェットを使ってアプリケーションバーをおきます。
アプリケーションバーにはleading
プロパティでIconButton
クラスを呼び出します。
leading
プロパティはアプリケーションバーの左側にアイコンを表示できます。
onPressed
プロパティはアイコンがタップされたときに実行される動作を指定します。
とりあえず、(){}
としてタップだけ有効にしておきます。
icon
プロパティでIcon
クラスを呼び出しIcons.settings
を表示します。
Icons.settings
は歯車のアイコンを表示します。
あとで歯車のアイコンをタップして設定画面に遷移するようにコードを追加します。
次にCenter
ウィジェットを使って画面中央にテキストを置くことができます。
そしてText
ウィジェットを使って各テキストを表示します。
style
プロパティからTextStyle
クラスを呼び出してfontSize
プロパティを60
にしてフォントサイズを変更します。
また"PageA"
だけcolor
プロパティをColors.red
にしてフォントカラーを赤色にして区別しやすくします。
設定画面
設定画面をファイル名setting.dart
で作成します。
import "package:flutter/material.dart";
class SettingPage extends StatelessWidget {
const SettingPage({super.key});
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
children: const [
ListTile(
title: Text("起動時画面切り替え"),
subtitle: Text("起動時の画面をAとBで切り替えます"),
trailing: Switch(
value: true,
onChanged: null,
),
),
],
),
);
}
}
main.dartを変更
まずはmain.dart
を以下のようにスッキリしておきます。
import "package:flutter/material.dart";
import "package:settings_sample/views/page_a.dart";
void main() {
runApp(const MainApp());
}
class MainApp extends StatelessWidget {
const MainApp({super.key});
Widget build(BuildContext context) {
return const MaterialApp(
home: PageA(),
);
}
}
import 'package:settings_sample/views/page_a.dart';
を追加します。
page_a.dart
のPageA
ウィジェットを参照します。
MainApp
はStatelessWidget
に変更します。
MainApp
のMaterialApp
ウィジェットを返します。
Scaffold
ウィジェットを子要素としてbody
プロパティからPageA
ウィジェットを使用します。
これにより、PageA
がアプリケーションのメイン画面に表示されます。
設定画面に移動
歯車のアイコンをタップして設定画面へ移動できるようにします。
以下のFlutterのドキュメントを参考にして実装します。
ドキュメントを見ると以下のコードを追加するようになっています。
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const SecondRoute()),
);
}
ここではSecondRoute()
をSettingPage()
に置き換えて、page_a.dart
とpage_b.dart
のonPressed
に追加します。
onPressed: () {
+ Navigator.push(
+ context,
+ MaterialPageRoute(builder: (context) => const SettingPage()),
+ );
}
あと、setting.dart
をインポートします。
+ import "package:settings_sample/views/setting.dart";
ViewModelを作成
設定画面での設定状態をshared_preferences
で保存します。
保存したデータをprovider
で状態管理するためにsetting_model.dart
を作成します。
import "package:flutter/material.dart";
import "package:shared_preferences/shared_preferences.dart";
class SettingModel with ChangeNotifier {
// 初期値
bool _startPageA = true;
bool get startPageA => _startPageA;
Future<void> getSetting() async {
final prefs = await SharedPreferences.getInstance();
final startPageAValue = prefs.getBool("startPageA") ?? false;
_startPageA = startPageAValue;
notifyListeners();
}
Future<void> setStartPageA(bool value) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setBool("startPageA", value);
_startPageA = value;
notifyListeners();
}
}
アプリがPageA
で開始するかどうかを決定するbool
型の_startPageA
変数があります。
また初期値はtrue
にしてページAが表示されるようにしています。
getter
メソッドで_startPageA
の値を取得します。
getSetting()
メソッドはSharedPreferences
からユーザーの保存された設定を取得し_startPageA
変数を更新します。
setStartPageA()
メソッドは_startPageA
変数を更新しSharedPreferences
に保存します。
変数_startPageA
に変更がある場合にはnotifyListeners()
メソッドが呼び出されて状態に変更があったことをリスナーに通知します。
スイッチの設定
設定画面のスイッチを動作するようにsetting.dart
を変更します。
import "package:flutter/material.dart";
+ import "package:provider/provider.dart";
+ import "package:settings_sample/viewmodels/setting_model.dart";
class SettingPage extends StatelessWidget {
const SettingPage({super.key});
Widget build(BuildContext context) {
- return Scaffold(
- body: ListView(
- children: const [
- ListTile(
- title: Text("起動時画面切り替え"),
- subtitle: Text("起動時の画面をAとBで切り替えます"),
- trailing: Switch(
- value: true,
- onChanged: null,
- ),
- ),
- ],
- ),
- );
+ return ChangeNotifierProvider<SettingModel>(
+ create: (_) => SettingModel()..getSetting(),
+ child: Scaffold(
+ body: Consumer(
+ builder: (
+ BuildContext context,
+ SettingModel model,
+ Widget? child,
+ ) =>
+ ListView(
+ children: [
+ ListTile(
+ title: const Text("起動時画面切り替え"),
+ subtitle: const Text("起動時の画面をAとBで切り替えます"),
+ trailing: Switch(
+ value: model.startPageA,
+ onChanged: (value) => {
+ model.setStartPageA(value),
+ debugPrint(value.toString()),
+ }
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
}
}
SettingModel
クラスのインスタンスを作成しgetSetting()
メソッドを呼び出してアプリの設定情報を取得します。
そしてChangeNotifierProvider
を使用してSettingModel
クラスのインスタンスを作成します。
これによりアプリ内の他のウィジェットでSettingModel
クラスのインスタンスを使用して設定情報を取得できます。
Consumer
ウィジェットはChangeNotifierProvider
で状態を監視し変更された場合にUIを再ビルドするために使用されます。
このコードではSettingModel
クラスのインスタンスを使用してアプリの設定情報を取得しUIに反映します。
builder
プロパティはUIを構築するためのコールバック関数を指定します。
このコールバック関数はBuildContext
、SettingModel
、およびchild
ウィジェットを引数として受け取りUIを構築するために使用されます。
このように、Consumer
ウィジェットを使用することでアプリ内の他のウィジェットでSettingModel
クラスのインスタンスを使用してアプリの設定情報を取得できます。
SettingModel
クラスのstartPageA
プロパティを使用してスイッチの初期値を設定します。
onChanged
プロパティはスイッチの値が変更されたときに呼び出されるコールバック関数を指定します。
このコードではSettingModel
クラスのsetStartPageA
メソッドを使用してスイッチの値を更新します。
そしてdebugPrint
関数を使用してスイッチの値をデバッグコンソールに出力します。
それでは一度実行してみましょう。
そして設定画面に移動してスイッチを動かしてみます。
そうするとデバッグコンソールでスイッチを動かすとtrue
かfalse
を交互に表示します。
main.dartを再び変更
最後にトップページを設定の値によって変更されるようにします。
import "package:flutter/material.dart";
+ import "package:provider/provider.dart";
+ import "package:settings_sample/viewmodels/setting_model.dart";
import "package:settings_sample/views/page_a.dart";
+ import "package:settings_sample/views/page_b.dart";
void main() {
runApp(const MainApp());
}
class MainApp extends StatelessWidget {
const MainApp({super.key});
Widget build(BuildContext context) {
- return const MaterialApp(
+ return ChangeNotifierProvider<SettingModel>(
- home: PageA(),
+ create: (_) => SettingModel()..getSetting(),
+ child: MaterialApp(
+ home: Consumer(
+ builder: (
+ BuildContext context,
+ SettingModel model,
+ Widget? child,
+ ) =>
+ Scaffold(
+ body: model.startPageA ? const PageA() : const PageB(),
+ ),
+ ),
+ ),
);
}
}
setting.dart
を変更と同様にChangeNotifierProvider
を使用してSettingModel
クラスのインスタンスを作成してConsumer
ウィジェットを導入します。
body
プロパティには3項演算子を利用して"PageA"
と"PageB"
を切り替えます。
3項演算子とはif-else
文と同じように条件分岐を行う演算子です。
具体的には次のように書くことができます。
条件式 ? 式1 : 式2
この式では条件式がtrue
ならば式1が選択されます。条件式がfalse
ならば式2が選択されます。
model.startPageA ? const PageA() : const PageB()
今回の場合はmodel.startPageA
がtrue
でならばPageA()
をfalse
ならばPageB()
が選択されます。
これで設定画面から起動時の画面を設定できるようになりました。
スマホアプリ「ひとこと投資メモ」シリーズをリリース
Flutter学習のアウトプットの一環として「日本株ひとこと投資メモ」「米国株ひとこと投資メモ」を公開しています。
今回の設定機能の構築も活用しています。
簡単に使えるライトな投資メモアプリです。
iPhone、Android両方に対応しています。
みなさんの投資ライフに少しでも活用していただきれば幸いです。
以下のリンクからそれぞれのサイトに移動してダウンロードをお願いします。
Discussion