😇

初めてRiverPodを使ってアプリの初めの画面を作ってみたよ!

2023/10/30に公開

今回はRiverPodを使ってアプリの最初の画面を作っていきます!

初めに今回作りたいページの完成系はこちら

ステップ1 まずはUIを作ります

PageViewのコードが以下です

Welcome.dart

class Welcome extends StatelessWidget {
  Welcome({Key? key});

  final PageController _controller = PageController();

  
  Widget build(BuildContext context) {
    return Container(
      color: Colors.white, //Safeをラップして黒い部分をなくす
      child: SafeArea(
        child: Scaffold(
          backgroundColor: Colors.white,
          body: Container(
            margin: EdgeInsets.only(top: 30), //stackごトラップして高さをtop30
            child: Stack(
              alignment: Alignment.topCenter,
              children: [
                //showing our three welcome pages
                PageView(
                  onPageChanged: (value) {
                   
                  },
                  controller: _controller,
                  scrollDirection: Axis.horizontal, //横にスライド
                  children: [
                    //first page
                    appOnboardingPage(
                      _controller,
                      imagePath: 'assets/images/reading.png',
                      title: 'ようこそ!',
                      subtitle: 'アプリの説明1',
                      index: 1,
                      context: context,
                    ),
                    //second page
                    appOnboardingPage(
                      _controller,
                      imagePath: 'assets/images/man.png',
                      title: 'これは2ページ目',
                      subtitle: 'アプリの説明2',
                      index: 2,
                      context: context,
                    ),
                    appOnboardingPage(
                      _controller,
                      imagePath: 'assets/images/boy.png',
                      title: 'これは3ページ目',
                      subtitle: 'アプリの説明3',
                      index: 3,
                      context: context,
                    ),
                  ],
                ),
                //for showing dots
                Positioned(
                  bottom: 50,
                  child: DotsIndicator(
                    position: index,
                    dotsCount: 3, //ドットの数
                    mainAxisAlignment: MainAxisAlignment.center,
                    decorator: DotsDecorator(
                      size: const Size.square(15.0),
                      activeSize: const Size(24.0, 8.0), //アクティブなドットのサイズ
                      activeShape: RoundedRectangleBorder(
                        // アクティブなドットのデフォルトの形状(ShapeBorder)
                        borderRadius: BorderRadius.circular(5),
                      ),
                    ),
                  ),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}
appOnboardingPageのコード
widgets.dart
//PageViewのページ
Widget appOnboardingPage(PageController controller,
    {required String imagePath,
    required String title,
    required String subtitle,
    required int index,
    required BuildContext context}) {
  return Column(
    children: [
      Image.asset(
        imagePath,
        fit: BoxFit.fitWidth,
      ),
      Container(
        margin: EdgeInsets.only(top: 15),
        child: text24Normal(text: title),
      ),
      Container(
        margin: EdgeInsets.only(top: 15),
        padding: EdgeInsets.only(left: 30, right: 30),
        child: text16Normal(text: subtitle),
      ),
      _nextButton(index, controller, context),
    ],
  );
}

//次へボタン
Widget _nextButton(int index, PageController controller, BuildContext context) {
  return GestureDetector(
    onTap: () async {
      if (index < 3) {
      } else {
        Navigator.pushNamed(
          context,
          "/signIn",
        );
      }
    },
    child: Container(
      width: 325,
      height: 50,
      margin: EdgeInsets.only(top: 100, left: 25, right: 25),
      decoration: appBoxShadow(),
      child: Center(
        child: text16Normal(
	//indexが3になったらtextをGet startedにする
          text: index < 3 ? "次へ" : "Get started",
          color: Colors.white,
        ),
      ),
    ),
  );
}

ステップ2 ここで遂にRiverpodの導入です

まずはpubspec.yaml

pubspec.yaml
  cupertino_icons: ^1.0.2
  flutter_riverpod: ^2.4.4
  dots_indicator: ^3.0.0
  riverpod_generator: ^2.3.5
  build_runner: ^2.4.6

インストールします!

 flutter pub get

Riverpodを使用して初めのページを実行する

ProviderScopeでMyAppを囲む

main.dart
void main() {
  runApp(const ProviderScope(child: MyApp()));
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp(
      initialRoute: "/",
      routes: {
        "/": (context) => Welcome(),
        "/signIn": (context) => SignIn(),
      },
      // home: Welcome(),
    );
  }
}

初期値を設定

changeIndex メソッドを使用して state を変更することで、Riverpodのプロバイダに新しい値が提供され、これにより状態変更をトリガーします。したがって、state を使用することは、アプリの状態管理に一貫性と効率性をもたらし、UIのリアクティビティを実現する

import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'welcome_notifier.g.dart';


class IndexDot extends _$IndexDot {
  
  int build() {
  //初期値を設定 
    return 0;
  }

  void changeIndex(int value) {
  ////state にvalueを格納
    state = value;
  }
}

Riverpodのコード生成

そしてコマンドでコード生成します!

 flutter pub run build_runner watch --delete-conflicting-outputs

すると,,,

// GENERATED CODE - DO NOT MODIFY BY HAND

part of 'welcome_notifier.dart';

// **************************************************************************
// RiverpodGenerator
// **************************************************************************

String _$indexDotHash() => r'e407c19e35a91274d4ef23f17c8ec0cedde72385';

/// See also [IndexDot].
(IndexDot)
final indexDotProvider = AutoDisposeNotifierProvider<IndexDot, int>.internal(
  IndexDot.new,
  name: r'indexDotProvider',
  debugGetCreateSourceHash:
      const bool.fromEnvironment('dart.vm.product') ? null : _$indexDotHash,
  dependencies: null,
  allTransitiveDependencies: null,
);

typedef _$IndexDot = AutoDisposeNotifier<int>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member

簡単解説
IndexDotクラスを基にして生成されたindexDotProvider は 、特定の型(今回はint)の状態を提供するプロバイダです。このプロバイダはRiverpodの状態管理を支援するために使用され、アプリケーション内での状態の管理に役立ちます。コード生成によって、状態の作成と管理が簡素化され、コードが簡潔になります。

コード生成が完了したら

ステップ3 適切な箇所にコードを追加する!

- StatelessWidget
+ ConsumerWidgetに変更する
class Welcome extends ConsumerWidget {
  Welcome({Key? key});

  final PageController _controller = PageController();

  
+ WidgetRef ref
  Widget build(BuildContext context, WidgetRef ref) {
    //indexDotProvider を監視して、その現在の状態を index 変数に割り当てています。
    // index は、indexDotProvider の現在の値を示すために使用されます。
+  final index = ref.watch(indexDotProvider);//indexDotProviderの状態をwatch
    return Container(
      color: Colors.white, //Safeをラップして黒い部分をなくす
      child: SafeArea(
        child: Scaffold(
          backgroundColor: Colors.white,
          body: Container(
            margin: EdgeInsets.only(top: 30), //stackごトラップして高さをtop30
            child: Stack(
              alignment: Alignment.topCenter,
              children: [
                //showing our three welcome pages
                PageView(
                  onPageChanged: (value) {
		  //changeIndexが呼ばれる
+ ref.read(indexDotProvider.notifier).changeIndex(value);//notifierをread
                  },
                  controller: _controller,
                  scrollDirection: Axis.horizontal, //横にスライド
                  children: [
                    //first page
                    appOnboardingPage(
                      _controller,
                      imagePath: 'assets/images/reading.png',
                      title: 'ようこそ!',
                      subtitle: 'アプリの説明1',
                      index: 1,
                      context: context,
                    ),
                    //second page
                    appOnboardingPage(
                      _controller,
                      imagePath: 'assets/images/man.png',
                      title: 'これは2ページ目',
                      subtitle: 'アプリの説明2',
                      index: 2,
                      context: context,
                    ),
                    appOnboardingPage(
                      _controller,
                      imagePath: 'assets/images/boy.png',
                      title: 'これは3ページ目',
                      subtitle: 'アプリの説明3',
                      index: 3,
                      context: context,
                    ),
                  ],
                ),
                //for showing dots
                Positioned(
                  bottom: 50,
                  child: DotsIndicator(
                    position: index,
                    dotsCount: 3, //ドットの数
                    mainAxisAlignment: MainAxisAlignment.center,
                    decorator: DotsDecorator(
                      size: const Size.square(15.0),
                      activeSize: const Size(24.0, 8.0), //アクティブなドットのサイズ
                      activeShape: RoundedRectangleBorder(
                        // アクティブなドットのデフォルトの形状(ShapeBorder)
                        borderRadius: BorderRadius.circular(5),
                      ),
                    ),
                  ),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}


Widget appOnboardingPage(PageController controller,
    {required String imagePath,
    required String title,
    required String subtitle,
    required int index,
    required BuildContext context}) {
  return Column(
    children: [
      Image.asset(
        imagePath,
        fit: BoxFit.fitWidth,
      ),
      Container(
        margin: EdgeInsets.only(top: 15),
        child: text24Normal(text: title),
      ),
      Container(
        margin: EdgeInsets.only(top: 15),
        padding: EdgeInsets.only(left: 30, right: 30),
        child: text16Normal(text: subtitle),
      ),
      _nextButton(index, controller, context),
    ],
  );
}

Widget _nextButton(int index, PageController controller, BuildContext context) {
  return GestureDetector(
    onTap: () async {
      if (index < 3) {
+ await controller.animateToPage(index,duration: const Duration(milliseconds: 300), curve: Curves.linear);
      } else {
        Navigator.pushNamed(
          context,
          "/signIn",
        );
      }
    },
    child: Container(
      width: 325,
      height: 50,
      margin: EdgeInsets.only(top: 100, left: 25, right: 25),
      decoration: appBoxShadow(),
      child: Center(
        child: text16Normal(
          text: index < 3 ? "次へ" : "Get started",
          color: Colors.white,
        ),
      ),
    ),
  );
}

動きをステップバイステップで解説します

まず最初は次へボタンが押された時の動き

  1. indexは1が入ってくる。if (index < 3) の条件をチェックします。indexが3未満であれば、animateToPageを使って次のページに移動します。
  2. その次にPageViewのonPageChangedのvlueに1が入ってくる、 ref.read(indexDotProvider.notifier).changeIndex(value);
    が呼ばれてstateにvalueの1が入ります!
    3.そして3ページ目まで移動した時にonTapが呼ばれるとindexには3が入るので、条件チェックでログインページに画面遷移します

感想

これが噂のRiverpodか!!!
コードもシンプルに書けていいと思いました。
なのでもっと使って知識をつけていきたいと思います!

最後まで読んでいただきありがとうございました!!!

Discussion