Динамически добавлять элементы в ListView из Redux - PullRequest
0 голосов
/ 04 марта 2020

создание приложения для социальной сети У меня следующая проблема:

На одном из экранов есть Лента . Некоторые сообщения взяты из 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));
      },
    );
  }
}
...