-
Notifications
You must be signed in to change notification settings - Fork 0
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
ユーザー画面 #39
Conversation
There was a problem hiding this 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月中にコース修了してゴールデンウィークはたくさん遊びましょう🔥
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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ローディングの状態管理で使われる変数がisLoading
とisFetching
の2種類定義されてしまっています。
今回修正の必要はありませんが、混乱やバグの原因になるので、今後は1つの役割に対して変数を複数定義しないようにしてください。
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; | ||
}); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
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文の中はシンプルに
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文にまとめて書くことにより、コードの行数を削減したり、修正が用意になったりします。
|
||
Future<void> fetchUserArticles() async { | ||
setState(() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
もう少し、記事の追加読み込みを行なっていることがわかるようなメソッド名の方が良いのかなと思いました。
処理の中身に関しては、fetchUserInfoメソッドと同じことが言えます。
@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, | ||
), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
1つの画面にScafolldが3つあり、AppTitleもほぼ同じ内容なので、条件分岐を工夫すればもっと上手く共通化できるかもしれませんね。
今回修正の必要はないです。
qiita_app/lib/pages/user_page.dart
Outdated
Widget _buildLoadMoreIndicator() { | ||
return Center( | ||
child: isFetching | ||
? const CircularProgressIndicator() | ||
: ElevatedButton( | ||
onPressed: () { | ||
currentPage++; | ||
fetchUserArticles(); | ||
}, | ||
child: const Text('もっと読み込む'), | ||
), | ||
); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
仕様では、ボタンではなくスクロールによるページネーションだったはずです。
動作確認用かもしれませんが、仕様と異なるので修正お願いします🙏
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); | ||
} |
There was a problem hiding this comment.
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'みたいなスペルミスも防ぐことができます。
LGTM🎉 |
概要
フォロー・フォロワーを押して表示されるユーザー画面を作成しました。
CLIENT_IDとCLIENT_SECRET
実装した内容
UI
動作確認
Screen_recording_20240414_195018.webm
参考
その他