Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ユーザー画面 #39

Merged
merged 5 commits into from
Apr 21, 2024
Merged

ユーザー画面 #39

merged 5 commits into from
Apr 21, 2024

Conversation

IwaseMio
Copy link
Collaborator

@IwaseMio IwaseMio commented Apr 20, 2024

概要

フォロー・フォロワーを押して表示されるユーザー画面を作成しました。

CLIENT_IDとCLIENT_SECRET

実装した内容

UI

動作確認

Screen_recording_20240414_195018.webm

参考

その他

@IwaseMio IwaseMio linked an issue Apr 21, 2024 that may be closed by this pull request
@IwaseMio IwaseMio self-assigned this Apr 21, 2024
Copy link
Collaborator

@KobayashiYoh KobayashiYoh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

今回修正してほしいこと

仕様と異なる1点のみ修正お願いします。
今後の案件を見据えて細かくコメントしましたが、他はコース修了後に時間があれば取り組んでみてください。

次回のプルリク提出でやってほしいこと

初回レビュワーの方が必ず必要になるので、プルリクの「CLIENT_IDとCLIENT_SECRET」の項目に.envのダウンロードリンクを貼っておきましょう。

よかった点

技術力や自走力が上がってきた点に加え、実装が丁寧でバグが少ない点が素晴らしいなと思いました!
ユーザー画面は、バグがあるのに気づかずプルリクを出してしまいがちですが、今回バグがなかったので、Mioさんは丁寧に動作確認を行いながら実装を頑張ったんだなというのが伝わってきました!
4月中にコース修了してゴールデンウィークはたくさん遊びましょう🔥

Comment on lines +20 to +27
class _UserPageState extends State<UserPage> {
List<Article> articles = [];
User? loggedInUser;
bool isLoading = true;
bool hasNetworkError = false;
bool isFetching = false;
int currentPage = 1;
late ScrollController _scrollController;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ローディングの状態管理で使われる変数がisLoadingisFetchingの2種類定義されてしまっています。
今回修正の必要はありませんが、混乱やバグの原因になるので、今後は1つの役割に対して変数を複数定義しないようにしてください。

Comment on lines +37 to +66
Future<void> fetchUserInfo() async {
setState(() {
isLoading = true;
});

try {
User userInfo = await QiitaRepository.fetchUserInfo(widget.userId);
List<Article> userArticles = await QiitaRepository.fetchUserArticles(
widget.userId,
page: currentPage);

if (mounted) {
setState(() {
loggedInUser = userInfo;
articles = userArticles;
isLoading = false;
hasNetworkError = false;
});
}
} catch (e) {
debugPrint('Failed to fetch user info: $e');
if (mounted) {
setState(() {
loggedInUser = null;
isLoading = false;
hasNetworkError = true;
});
}
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

僕だったらこのfetchUserInfoというメソッドを以下のように実装するかなと思います。

  Future<void> fetchUserInfo() async {
    if (isLoading) return;
    setState(() {
      isLoading = true;
      hasNetworkError = false;
    });
    User? user;
    List<Article> userArticles = [];
    try {
      user = await QiitaRepository.fetchUserInfo(widget.userId);
      userArticles = await QiitaRepository.fetchUserArticles(
        widget.userId,
        page: currentPage,
      );
    } catch (e) {
      debugPrint('Failed to fetch user info: $e');
      setState(() {
        loggedInUser = null;
        hasNetworkError = true;
      });
    } finally {
      setState(() {
        isLoading = false;
      });
    }
    setState(() {
      loggedInUser = user;
      articles = userArticles;
    });
  }

ポイントは以下の4点です。

1. try文の中はシンプルに

#38 (comment) 参照

2. 早期リターンを使う

#38 (comment) 参照
ローディング中にもかかわらず、fetchUserInfoメソッドが実行されるのを防ぐために1番最初に if (isLoading) return;という処理を記述しています。

3. 変数名をuserInfoからuserに変更

変数名がuserInfoだと、ユーザーと記事の両方の値を格納しているのか誤解を与えてしまう恐れがあります。
fetchUserInfoで取得したユーザーを格納する変数なのでuserにしました。

4. try-catch-finally文のfinally句を使う

try-catch文の後ろにfinally句が追加されたtry-catch-finally文という構文がDartを始めとしたプログラミング言語には用意されています。
finally内に書いた処理は、try内で生じたエラーの有無にかかわらず、最終的に必ず実行されます。
今回の例だと、ユーザー情報の取得に成功しても、失敗しても、最終的にisLoadingをfalseにする実装が必要になります。
これをそれぞれ別の場所に書くのではなく、finally文にまとめて書くことにより、コードの行数を削減したり、修正が用意になったりします。

Comment on lines +76 to +78

Future<void> fetchUserArticles() async {
setState(() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

もう少し、記事の追加読み込みを行なっていることがわかるようなメソッド名の方が良いのかなと思いました。
処理の中身に関しては、fetchUserInfoメソッドと同じことが言えます。

Comment on lines +103 to +132
@override
Widget build(BuildContext context) {
if (isLoading) {
return const Scaffold(
appBar: AppTitle(
title: 'UserPage',
showBottomDivider: true,
),
body: Center(child: CircularProgressIndicator()),
);
}
if (hasNetworkError) {
return Scaffold(
appBar: const AppTitle(title: 'UserPage', showBottomDivider: true),
body: NetworkError(
onPressReload: () {
setState(() {
hasNetworkError = false;
});
fetchUserInfo();
},
),
);
}
return Scaffold(
appBar: const AppTitle(
title: 'UserPage',
showBottomDivider: true,
showReturnIcon: true,
),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1つの画面にScafolldが3つあり、AppTitleもほぼ同じ内容なので、条件分岐を工夫すればもっと上手く共通化できるかもしれませんね。
今回修正の必要はないです。

Comment on lines 158 to 171
Widget _buildLoadMoreIndicator() {
return Center(
child: isFetching
? const CircularProgressIndicator()
: ElevatedButton(
onPressed: () {
currentPage++;
fetchUserArticles();
},
child: const Text('もっと読み込む'),
),
);
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

仕様では、ボタンではなくスクロールによるページネーションだったはずです。
動作確認用かもしれませんが、仕様と異なるので修正お願いします🙏

Comment on lines 70 to 74
if (widget.listType == 'following') {
fetchedUsers =
await QiitaRepository.fetchFollowingUsers(authenticatedUser.id);
fetchedUsers = await QiitaRepository.fetchFollowingUsers(widget.userId);
} else {
fetchedUsers =
await QiitaRepository.fetchFollowersUsers(authenticatedUser.id);
fetchedUsers = await QiitaRepository.fetchFollowersUsers(widget.userId);
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

本当に文字列listTypeがfollowing以外だった場合、全てfollowerとなるでしょうか?
この場合、文字列'hoge'のようなテキトーな文字列でもfollowing以外と判定され、follwer扱いになってしまいます。

それを防ぐために、以下のようにenumを活用してみるといいかもしれません。

enum FollowerFollowingListType {
  follower,
  following;
}

class FollowerFollowingListPage extends StatefulWidget {
  final FollowerFollowingListType listType;
  final String userId;

// 中略

      List<User> fetchedUsers = [];
      if (widget.listType == FollowerFollowingListType.following) {
        fetchedUsers = await QiitaRepository.fetchFollowingUsers(widget.userId);
      } else if (widget.listType == FollowerFollowingListType.follower) {
        fetchedUsers = await QiitaRepository.fetchFollowersUsers(widget.userId);
      }

String型だと任意の文字列をFollowerFollowingListPageクラスに渡すことができますが、新しく定義したFollowerFollowingListType型はfollowerとfollowingしか存在しないため、先ほどの'hoge'のような異物を弾くことができます。
異物を弾くことができるので、`followor'みたいなスペルミスも防ぐことができます。

@KobayashiYoh
Copy link
Collaborator

LGTM🎉

@KobayashiYoh KobayashiYoh merged commit d6125b2 into main Apr 21, 2024
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

ユーザー画面
2 participants