🐧

Joke API × Riverpodで学ぶFlutter入門|素晴らしいジョークで状態管理を2%理解する

に公開

Flutter × Riverpod の学習として、外部APIを使ったアプリを作ってみました
Riverpodを優しく理解するために作成しているので主にリバーポッド部分の解説になります。

https://riverpod.dev/ja/

使用した
-Flutter
-Riverpod(FutureProvider)
-Dio(API通信)
-Official Joke API(https://official-joke-api.appspot.com
-Sourcetree(Git管理)

📦 jokeapiriverpod
Flutter + Riverpod を使ったジョークアプリです。
GitHub: yoichi4141/jokeapiriverpod

アプリの内容
-ジョークを1つ取得して表示
-「もっと💩」ボタンで再取得

コード解説 🔍
-モデルの定義(Jokeクラス)
-jokeProvider(FutureProviderで非同期取得)
-フィルター機能(containsPoop関数とwhileループ)
-UI(ConsumerWidget)

完成イメージ

面白すぎるジョークが展開される

コード↓
main:

//main.dart
import 'package:flutter/material.dart';
import 'package:dio/dio.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fluttercook/providers/joke_provider.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  
  Widget build(BuildContext context) {
    return const MaterialApp(home: JokeScreen());
  }
}

class JokeScreen extends ConsumerWidget {
  const JokeScreen({super.key});

  
  Widget build(BuildContext context, WidgetRef ref) {
    final jokeAsync = ref.watch(jokeProvider);

    return Scaffold(
      appBar: AppBar(title: const Text('💩ジョーク with Riverpod')),
      body: jokeAsync.when(
        loading: () => const Center(child: CircularProgressIndicator()),
        error:
            (err, _) =>
                Center(child: Text('ohhhhhh!!!shiiiit!!!!!!error!!!!!!!!')),
        data:
            (joke) => Padding(
              padding: const EdgeInsets.all(24),
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Text(
                    joke.setup,
                    style: const TextStyle(fontSize: 20),
                    textAlign: TextAlign.center,
                  ),
                  const SizedBox(height: 20),
                  Text(
                    joke.punchline,
                    style: const TextStyle(
                      fontSize: 24,
                      fontWeight: FontWeight.bold,
                    ),
                    textAlign: TextAlign.center,
                  ),
                  const SizedBox(height: 40),
                  ElevatedButton(
                    onPressed: () => ref.refresh(jokeProvider),
                    child: const Text('もっと💩'),
                  ),
                ],
              ),
            ),
      ),
    );
  }
}

model:

//joke.dart
class Joke {
  final String setup;
  final String punchline;

  Joke({required this.setup, required this.punchline});

  factory Joke.fromJson(Map<String, dynamic> json) {
    return Joke(setup: json['setup'], punchline: json['punchline']);
  }
}

provider:

//joke_provider.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:dio/dio.dart';
import 'package:fluttercook/model/joke.dart';

//Dioクライアント
final dioProvider = Provider((ref) => Dio());

//FutureProviderでジョーク取得
final jokeProvider = FutureProvider<Joke>((ref) async {
  final dio = ref.watch(dioProvider);
  final response = await dio.get(
    'https://official-joke-api.appspot.com/jokes/random',
  );
  return Joke.fromJson(response.data);
});

覚えておきたいこと

・refはRiverpodによって内部で管理されており、各プロバイダーに自動で渡される特別な引数
・「refに格納する」ではなく→「refで参照する」「refで依存を定義する(宣言する)」

イメージ:

refは「テレビのリモコン」
dioProvider は「チャンネル(Dio)」
ref.watch(dioProvider) は「そのチャンネルを見る操作」
でもリモコン自体に番組が格納されてるわけじゃない

Discussion