构建一个社交网络应用程序,我遇到以下问题:
其中一个屏幕具有 Feed 。有些帖子是从API中获取的,但可用于分页。第一个请求获得前10个帖子。一旦到达ListView
的底部,它将获取接下来的10条帖子,依此类推。
问题是,如何在不重建页面的情况下将新获取的帖子追加到ListView中。由于数据来自Redux状态。现在可以使用,但是当从API获取新帖子时,滚动位置将返回顶部,因为整个列表都已重新构建。这是代码:
app_state.dart
:
@immutable
class AppState {
final FeedState feedState;
AppState({
@required this.feedState,
});
factory AppState.initial() {
return AppState(
feedState: FeedState.initial(),
);
}
AppState copyWith({
FeedState feedState,
}) {
return AppState(
feedState: feedState ?? this.feedState,
);
}
}
@immutable
class FeedState {
List<Post> feed;
bool error;
bool loading;
FeedState({
this.feed,
this.error,
this.loading,
});
factory FeedState.initial() {
return FeedState(
feed: null,
error: false,
loading: false,
);
}
FeedState copyWith({
List<Post> feed,
bool error,
bool loading,
}) {
return FeedState(
feed: feed ?? this.feed,
error: error ?? this.error,
loading: loading ?? this.loading,
);
}
}
feed_reducer.dart
:
final feedReducer = combineReducers<FeedState>([
TypedReducer<FeedState, FeedSuccessAction>(_feedSuccess),
TypedReducer<FeedState, FeedAppendSuccessAction>(_feedAppendSuccess),
TypedReducer<FeedState, FeedFailedAction>(_feedFailed),
TypedReducer<FeedState, StartLoadingAction>(_startLoading),
]);
FeedState _feedSuccess(FeedState state, FeedSuccessAction action) {
return state.copyWith(feed: action.feed, loading: false, error: false);
}
FeedState _feedAppendSuccess(FeedState state, FeedAppendSuccessAction action) {
return state.copyWith(
feed: state.feed + action.feed, loading: false, error: false);
}
FeedState _feedFailed(FeedState state, FeedFailedAction action) {
return state.copyWith(feed: null, loading: false, error: true);
}
FeedState _startLoading(FeedState state, StartLoadingAction action) {
return state.copyWith(loading: true, error: false);
}
feed_actions.dart
:
class StartLoadingAction {
StartLoadingAction();
}
class FeedSuccessAction {
final List<Post> feed;
FeedSuccessAction(this.feed);
}
class FeedAppendSuccessAction {
final List<Post> feed;
FeedAppendSuccessAction(this.feed);
}
class FeedFailedAction {
FeedFailedAction();
}
ThunkAction fetchFeed(int page) {
return (Store store) async {
new Future(() async {
store.dispatch(new StartLoadingAction());
try {
List<Post> feed = await Api().fetchFeed(page);
store.dispatch(new FeedSuccessAction(feed));
} catch (error) {
store.dispatch(new FeedFailedAction());
}
});
};
}
ThunkAction fetchAppendFeed(int page) {
return (Store store) async {
new Future(() async {
store.dispatch(new StartLoadingAction());
try {
List<Post> feed = await Api().fetchFeed(page);
store.dispatch(new FeedAppendSuccessAction(feed));
} catch (error) {
store.dispatch(new FeedFailedAction());
}
});
};
}
feed_screen.dart
:
class FeedScreen extends StatelessWidget {
final ScrollController feedController = ScrollController();
int page = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Feed"),
),
body: StoreConnector<AppState, FeedViewModel>(
converter: (Store<AppState> store) => FeedViewModel.fromStore(store),
builder: (BuildContext context, FeedViewModel vm) {
if (!vm.loading && !vm.error && vm.feed == null) {
vm.getFeed(0);
feedController.addListener(() => listener(vm.appendFeed));
}
if (vm.loading) return buildLoading();
if (vm.error) return buildError(vm);
if (vm.feed != null) {
return RefreshIndicator(
child: buildFeed(vm),
onRefresh: () {
page = 0;
vm.getFeed(page);
},
);
}
if (vm.feed != null && vm.feed.length <= 0) return buildInitial(vm);
return Container();
},
),
);
}
Widget buildFeed(FeedViewModel model) {
List<Feed> feed = model.feed;
return ListView.builder(
controller: feedController,
itemCount: feed.length,
itemBuilder: (context, index) {
... // code
},
);
}
class FeedViewModel {
final bool loading;
final bool error;
final List<Post> feed;
final Function(int) getFeed;
final Function(int) appendFeed;
FeedViewModel({
this.loading,
this.error,
this.feed,
this.getFeed,
this.appendFeed,
});
static FeedViewModel fromStore(Store<AppState> store) {
return FeedViewModel(
loading: store.state.feedState.loading,
error: store.state.feedState.error,
feed: store.state.feedState.feed,
getFeed: (int page) {
store.dispatch(fetchFeed(page));
},
appendFeed: (int page) {
store.dispatch(fetchAppendFeed(page));
},
);
}
}