-
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
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
import 'package:flutter/material.dart'; | ||
import 'package:qiita_app/models/article.dart'; | ||
import 'package:qiita_app/models/user.dart'; | ||
import 'package:qiita_app/repository/qiita_repository.dart'; | ||
import 'package:qiita_app/widgets/app_title.dart'; | ||
import 'package:qiita_app/widgets/article_container.dart'; | ||
import 'package:qiita_app/widgets/network_error.dart'; | ||
import 'package:qiita_app/widgets/section_divider.dart'; | ||
import 'package:qiita_app/widgets/user_info_container.dart'; | ||
|
||
class UserPage extends StatefulWidget { | ||
final String userId; | ||
|
||
const UserPage({required this.userId, Key? key}) : super(key: key); | ||
|
||
@override | ||
State<UserPage> createState() => _UserPageState(); | ||
} | ||
|
||
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; | ||
Comment on lines
+20
to
+27
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ローディングの状態管理で使われる変数が |
||
|
||
@override | ||
void initState() { | ||
super.initState(); | ||
_scrollController = ScrollController(); | ||
fetchUserInfo(); | ||
_scrollController.addListener(_scrollListener); | ||
} | ||
|
||
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; | ||
}); | ||
} | ||
} | ||
} | ||
Comment on lines
+37
to
+66
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) 参照 3. 変数名をuserInfoからuserに変更変数名がuserInfoだと、ユーザーと記事の両方の値を格納しているのか誤解を与えてしまう恐れがあります。 4. try-catch-finally文のfinally句を使うtry-catch文の後ろにfinally句が追加されたtry-catch-finally文という構文がDartを始めとしたプログラミング言語には用意されています。 |
||
|
||
void _scrollListener() { | ||
if (!isFetching && | ||
_scrollController.position.pixels == | ||
_scrollController.position.maxScrollExtent) { | ||
currentPage++; | ||
fetchUserArticles(); | ||
} | ||
} | ||
|
||
Future<void> fetchUserArticles() async { | ||
setState(() { | ||
Comment on lines
+76
to
+78
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. もう少し、記事の追加読み込みを行なっていることがわかるようなメソッド名の方が良いのかなと思いました。 |
||
isFetching = true; | ||
}); | ||
|
||
try { | ||
List<Article> additionalArticles = | ||
await QiitaRepository.fetchUserArticles(widget.userId, | ||
page: currentPage); | ||
|
||
if (mounted) { | ||
setState(() { | ||
articles.addAll(additionalArticles); | ||
isFetching = false; | ||
}); | ||
} | ||
} catch (e) { | ||
debugPrint('Failed to fetch additional user articles: $e'); | ||
if (mounted) { | ||
setState(() { | ||
isFetching = false; | ||
}); | ||
} | ||
} | ||
} | ||
|
||
@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, | ||
), | ||
Comment on lines
+103
to
+132
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 1つの画面にScafolldが3つあり、AppTitleもほぼ同じ内容なので、条件分岐を工夫すればもっと上手く共通化できるかもしれませんね。 |
||
body: Column( | ||
children: [ | ||
if (loggedInUser != null) UserInfoContainer(user: loggedInUser!), | ||
const SectionDivider(text: '投稿記事'), | ||
Expanded( | ||
child: ListView.builder( | ||
itemCount: articles.length + 1, // +1 for loading indicator | ||
itemBuilder: (context, index) { | ||
if (index < articles.length) { | ||
return ArticleContainer( | ||
article: articles[index], | ||
showAvatar: false, | ||
); | ||
} else { | ||
return _buildLoadMoreIndicator(); | ||
} | ||
}, | ||
controller: _scrollController, | ||
), | ||
), | ||
], | ||
), | ||
); | ||
} | ||
|
||
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 commentThe reason will be displayed to describe this comment to others. Learn more. 仕様では、ボタンではなくスクロールによるページネーションだったはずです。 |
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を活用してみるといいかもしれません。
String型だと任意の文字列をFollowerFollowingListPageクラスに渡すことができますが、新しく定義したFollowerFollowingListType型はfollowerとfollowingしか存在しないため、先ほどの'hoge'のような異物を弾くことができます。
異物を弾くことができるので、`followor'みたいなスペルミスも防ぐことができます。