Динамическое добавление элементов в ListView из Redux
При создании приложения для социальной сети у меня возникла следующая проблема:
На одном из экранов есть 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));
},
);
}
}