marcglasberg / async_redux

Flutter Package: A Redux version tailored for Flutter, which is easy to learn, to use, to test, and has no boilerplate. Allows for both sync and async reducers.
Other
234 stars 40 forks source link

StoreConnector for each view of a GridView is refreshing wrong data model when items from GridView are updated #44

Closed sokarcreative closed 4 years ago

sokarcreative commented 4 years ago

Hi,

I'm new in Flutter and I really like this library, very intuitive and easy to use indeed. However I'm facing an issue when I'm using a StoreConnector for each view of a GridView which is himself affected by a StoreConnector when data list changed.

I have in my flutter app a tab for favorites and his screen shows a gridView of items (Sheets)

favorites_view_model.dart

import 'package:async_redux/async_redux.dart';
import 'package:business/app_state.dart';
import 'package:business/sheets/models/sheet.dart';
import 'package:business/view_state/models/view_state.dart';
import 'package:flutter/foundation.dart';

class FavoritesViewModel extends BaseModel<AppState>{

  FavoritesViewModel();

  ViewState<List<Sheet>> viewStateSheets;
  Set<int> favorites;

  FavoritesViewModel.build({
    @required this.viewStateSheets,
    @required this.favorites,
  });

  @override
  FavoritesViewModel fromStore() => FavoritesViewModel.build(viewStateSheets: state.viewStateSheets, favorites: state.favorites);

  @override
  bool operator == (Object other) {
    return identical(this, other) || other is FavoritesViewModel && runtimeType == other.runtimeType &&
    viewStateSheets == other.viewStateSheets && favorites == other.favorites && setEquals(favorites, other.favorites);
  }

  @override
  int get hashCode => viewStateSheets.hashCode ^ favorites.hashCode;
}

For each view inside my GridView, I'm using this SheetViewModel :

sheet_view_model.dart

import 'package:async_redux/async_redux.dart';
import 'package:business/app_state.dart';
import 'package:business/sheets/models/sheet.dart';
import 'package:business/view_state/models/view_state.dart';
import 'package:flutter/foundation.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';

class SheetViewModel extends BaseModel<AppState>{

  Sheet sheet;
  bool isLiked;
  LatLng userLocation;

  SheetViewModel({this.sheet});

  SheetViewModel.build({
    @required this.sheet,
    @required this.isLiked,
    @required this.userLocation,
  });

  @override
  SheetViewModel fromStore() => SheetViewModel.build(sheet: sheet, isLiked: state.favorites.contains(sheet.id), userLocation: state.userLocation);

  @override
  bool operator == (Object other) => identical(this, other) || other is SheetViewModel && runtimeType == other.runtimeType &&
      sheet == other.sheet && isLiked == other.isLiked && userLocation == other.userLocation;

  @override
  int get hashCode => sheet.hashCode ^ isLiked.hashCode ^ userLocation.hashCode;
}

The widgets (I'm using this file in favorites screen and list screen) :

gridview_sheets_widget.dart

import 'dart:math';
import 'package:async_redux/async_redux.dart';
import 'package:business/app_state.dart';
import 'package:client/sheet_view_model.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:business/sheets/models/sheet.dart';
import 'package:client/experimentations/triangle_percentage.dart';
import 'package:client/roots/root_screen_widget.dart';
import 'package:business/favorites/actions/add_favorite_action.dart';
import 'package:business/favorites/actions/remove_favorite_action.dart';

import '../app.dart';

const double commonPadding = 8;

GridView gridViewSheetsWidget(BuildContext context, List<Sheet> items, OnSheetClick onSheetClick,
    {double paddingTop = commonPadding * 2, double paddingBottom = commonPadding * 2}) {
  return GridView.builder(
      gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2, childAspectRatio: 2 / 3 - 1 / 11),
      itemCount: items.length,
      padding: EdgeInsets.only(top: paddingTop, bottom: paddingBottom /*vertical: 0commonPadding * 2*/),
      itemBuilder: (BuildContext context, int position) {
        return _getSheetViewStoreConnector(context, items, position, onSheetClick);
      });
}

Widget _getSheetViewStoreConnector(BuildContext context, List<Sheet> items, int position, OnSheetClick onSheetClick) {
  return StoreConnector<AppState, SheetViewModel>(
      model: SheetViewModel(sheet: items[position]),
      distinct: true,
      builder: (BuildContext context, SheetViewModel vm) => _getSheetViewHolderWidget(context, position, vm.sheet, vm.isLiked, vm.userLocation, onSheetClick)
  );
}

Widget _getSheetViewHolderWidget(BuildContext context, int position, Sheet sheet, bool isLiked, LatLng userLocation, OnSheetClick onSheetClick){
...
}
=> add one sheet to favorite
I/flutter ( 5338): Model D:22 R:22 = Rebuid:true, , Model:FavoritesViewModel{}.
I/flutter ( 5338): Model D:22 R:22 = Rebuid:true, , Model:SheetViewModel{}.
I/flutter ( 5338): Model D:22 R:22 = Rebuid:true, , Model:SheetViewModel{}.
I/flutter ( 5338): favorites: (59: Fragonard Parfumeur, 2299: AVEL CHAR A VOILE, 2300: LE ROC DES HARMONIES)
=> remove one sheet from favorites view
I/flutter ( 5338): Model D:23 R:23 = Rebuid:true, , Model:FavoritesViewModel{}.
I/flutter ( 5338): Model D:23 R:23 = Rebuid:true, , Model:SheetViewModel{}.
I/flutter ( 5338): Model D:23 R:23 = Rebuid:false, , Model:SheetViewModel{}.
I/flutter ( 5338): Model D:23 R:23 = Rebuid:false, , Model:SheetViewModel{}.
I/flutter ( 5338): favorites: (2299: AVEL CHAR A VOILE, 2300: LE ROC DES HARMONIES)

Some sheets (items) are not showing the correct sheet view when updating the GridView. It's like StoreConnector of each view was cached and not refreshed with the new sheet (item). I'm using GridView Builder.

Any help would be appreciated.

marcglasberg commented 4 years ago

I'll try to give you some general advice:

1) Redux will know when to rebuild a widget when the view model changes. For this to work, the view model's == must return false when it compares the current view model to the previous one and they are different. If the view model's == is not working as it should, it may not rebuild the widgets.

2) The view model's == will compare some state. This means the state's == must also be working as it should. For example, your ViewState class and your Sheet class must be immutable, and they must actually return false when you compare different objects.

Question: Are you sure your ViewState class and your Sheet class are immutable? Your actions must create new objects, and not mutate the existing ones.

If you can't find the problem by yourself, it's really difficult for me to help unless you provide a minimum runnable code, in a single file, with a main method.