Динамическое добавление элементов в 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));
      },
    );
  }
}

0 ответов

Другие вопросы по тегам