😯

The body might complete normally, causing 'null' to be returned, but t

2023/11/29に公開

riverpod generatorでFutureProviderを使いたい!

Firestoreから、データを取得するためにFutureProviderを使ったのですが、riverpod generatorに変更して使うと、エラーが出て自動生成させてくれない!

generatorなしのコード
問題はなかった....

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

import '../../../../application/helper/logger/logger.dart';
import '../../../entity/user/user.dart';
import '../../auth/auth_provider.dart';
import '../firebase_provider.dart';

FutureProvider<DocumentSnapshot<User>>
final userFutureProvider = FutureProvider((ref) async {
  final uid = ref.read(currentUserUidProvider);
  final serRef = await ref.read(converterProvider).doc(uid).get();
  return serRef;
});

ジェネレーターに変更するとエラーが出る???

The body might complete normally, causing 'null' to be returned, but the return type, 'FutureOr<DocumentSnapshot>', is a potentially non-nullable type. Try adding either a return or a throw statement at the end.dartbody_might_complete_normally

調べてみるとこんなことが分かりました!

Dart 2.12以降、null安全が導入され、デフォルトで変数はnullを許容しなくなりました。そのため、関数が非null型を返すことを宣言している場合、その関数は必ず非null値を返さなければなりません。

この問題を解決するためには、関数の最後にreturnまたはthrow文を追加することで、関数が必ず何かの値を返すようにする必要があります。

generatorありのコード
try catchthrowを追加すると解決できた。多分これで大丈夫と思われる。

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

import '../../../../application/helper/logger/logger.dart';
import '../../../entity/user/user.dart';
import '../../auth/auth_provider.dart';
import '../firebase_provider.dart';

part 'mypage.g.dart';


Future<DocumentSnapshot<User>> myPage(MyPageRef ref) async {
  try {
    final uid = ref.read(currentUserUidProvider);
    final serRef = await ref.read(converterProvider).doc(uid).get();
    return serRef;
  } catch (e) {
    throw Exception(e);
  } finally {
    logger.d('FutureProviderの処理を実行');
  }
}

他に使っているソースコードも記載しておく

FreezedwithConverterを使っているのでコード書いておきます。

Freezed
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:flutter/foundation.dart';

import '../converter/timestamp_converter.dart';

part 'user.freezed.dart';
part 'user.g.dart';

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


class User with _$User {
  const factory User({
    ('') String id,// documentId
    ('') String name,// ユーザーの名前
    () DateTime? birthday,// 生年月日
    ('') String job,// 職業
    ([]) List<String> followers,// フォローされている人
    ([]) List<String> follows,// フォローしている人
    ([]) List<String> invited,// 招待された人のフラグ
    ('') String imageUrl,// プロフィール画像のURL
    ('') String profile,// 自己紹介の文
    ('') String email, // ユーザーのメールアドレス
  }) = _User;

  factory User.fromJson(Map<String, dynamic> json) =>
      _$UserFromJson(json);
}
withConverterのプロバイダー
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

import '../../entity/user/user.dart';
part 'firebase_provider.g.dart';


FirebaseFirestore firebase(FirebaseRef ref) {
  return FirebaseFirestore.instance;
}


CollectionReference<User> converter(ConverterRef ref) {
  return ref.watch(firebaseProvider).collection('user').withConverter(
      fromFirestore: (snapshot, _) {
    final data = snapshot.data()!;
    final id = snapshot.id;
    // データがない場合は、空のUserをidだけセットして返す
    if (data.isEmpty) {
      return User(id: id);
    }
    data['id'] = id;
    return User.fromJson(data);
  }, toFirestore: (user, _) {
    return user.toJson();
  });
}


Stream<List<User>> userStream(UserStreamRef ref) {
  return ref.watch(converterProvider).snapshots().map((snapshot) {
    final users = snapshot.docs.map((doc) => doc.data()).toList();
    return users;
  });
}
Authのプロバイダー
import 'package:firebase_auth/firebase_auth.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'auth_provider.g.dart';

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

// FirebaseAuthを提供するProvider

FirebaseAuth firebaseAuth(FirebaseAuthRef ref) {
  return FirebaseAuth.instance;
}

// ログイン状態を監視するStreamを提供するProvider

Stream<User?> authStateChange(AuthStateChangeRef ref) {
  return ref.watch(firebaseAuthProvider).authStateChanges();
}


String? currentUserUid(CurrentUserUidRef ref) {
  return FirebaseAuth.instance.currentUser?.uid;
}
画像を表示するページ
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';

import '../../../domain/service/store/user/mypage.dart';
import '../../helper/theme/app_color.dart';

class MyPage extends HookConsumerWidget {
  const MyPage({super.key});

  
  Widget build(BuildContext context, WidgetRef ref) {
    final mypageRef = ref.watch(myPageProvider);
    return Scaffold(
      backgroundColor: AppColor.grey,
      appBar: AppBar(),
      body: SingleChildScrollView(
        child: Center(
          child: Column(
            children: [
              mypageRef.when(
                data: (prof) {
                  final pf = prof.data();
                  if (pf == null) {
                    return const Text('データがありません');
                  } else {
                    return Column(
                      children: [
                        const SizedBox(height: 50),
                        // 丸の画像を表示する
                        CircleAvatar(
                          radius: 50,
                          backgroundImage: CachedNetworkImageProvider(
                            pf.imageUrl,
                          ),
                        ),
                        Text(pf.name),
                        Text(pf.job),
                        Text(pf.profile),
                      ],
                    );
                  }
                },
                error: (error, stackTrace) => Text(error.toString()),
                loading: () => const CircularProgressIndicator(),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

こんな感じで画像とテキストを表示できます。

最後に

新しい書き方が推奨されるわけですけど、いざやってみると文法の知識が足りないのかすぐに書き換えらませんでしたね。
同じようなエラーが出たら、throwすればいいのだろうか...

今回は、画像の表示にはパッケージも使っているのでリンク貼っておきます。
https://pub.dev/packages/cached_network_image

解説によると...

CachedNetworkImage は、直接または ImageProvider 経由で使用できます。 CachedNetworkImage と CachedNetworkImageProvider は両方とも、Web に対する最小限のサポートを備えています。現在、キャッシュは含まれていません。

Image(image: CachedNetworkImageProvider(url))

Discussion