Search code examples
flutterdartstateproviderriverpod

Initialize StateProvider in FutureProvider


My app starts with retrieving data that is important throughout the flow of the app mainContent. Most of this data is static

Navigation screens

 Widget stackPages(WidgetRef ref) {
   AsyncValue<Map<String, Object>> mainContent = ref.watch(mainContentFutureProvider);
   return mainContent.when(
     loading: () => Center(child: CircularProgressIndicator()),
     error: (e, st) => Center(child: Text("Error: " + e.toString() + " " + st.toString())), 
     data: (content) {
        return Stack(
          children: [
            _buildOffstageNavigator(ref, "Home", content),
            _buildOffstageNavigator(ref, "Page1", content),
            _buildOffstageNavigator(ref, "Page2", content),
            _buildOffstageNavigator(ref, "Page3", content)
          ],
        );
      },
    );
  }

content retrieval (mainContentFutureProvider)

final mainContentFutureProvider= FutureProvider<Map<String, Object>>((ref) async {
    List response = await Future.wait([
       DataController.userInfoDB.getUsers(),
       DataController.userInfoDB.getAnotherList(),
       DataController.userInfoDB.getAnotherList,
    ]);

    return {
      "users": response[0],
      "some_list": response[1],
      "some_list": response[2],
    };
  },
);

User class (simplified)

class User{
  String id;
  String email;
  List<Vehicle> vehicles = [];

  User(this.email, this.vehicles);

  User.fromJson(Map<String, dynamic> json)
      : id = json['id'],
        displayName = json['display_name'],
}

problem

in the garage screen of the app the user can add or remove vehicles. When a user adds or removes a vehicle this affects the entire flow of the app. So this User needs to have its Notifier class

CurrentUserNotifier

class CurrentUserNotifier extends StateNotifier<User> {
  final User user;

  CurrentUserNotifier(this.user) : super(null);

  void addUserVehicle(Vehicle vehicle) {
    state..vehicles.add(vehicle);
  }

  void removeUserVehicle(int vehicleId) {
    state..vehicles.removeWhere((v) => v.id == vehicleId);
  }
}

currentUserProvider

final currentUserProvider = StateNotifierProvider.family<CurrentUserNotifier, User, User>((ref, user) {
  return CurrentUserNotifier(user);
});

Currently I am retrieving a List<User> and want only to have the current user to be coming from a provider in my app. As you see I have made a .family from StateNotifierProvider so I can perform the following thing:

content retrieval (mainContentFutureProvider)

final mainContentFutureProvider= FutureProvider<Map<String, Object>>((ref) async {
    List response = await Future.wait([
       DataController.userInfoDB.getUsers(),
       DataController.userInfoDB.getAnotherList(),
       DataController.userInfoDB.getAnotherList,
    ]);

    --->  currentUserProvider(response[0].first);

    return {
      "users": response[0],
      "some_list": response[1],
      "some_list": response[2],
    };
  },
);

But for any page that deals with my User object it needs to pass through the user object as parameter to my currentUserProvider

like:

press: () async {            
  ref.read(currentUserProvider(user).notifier).addUserVehicle(vehicle);
}

I want the provider just set the value of the StateNotifierProvider once, am I making a pattern/flow mistake here?


Solution

  • Try this:

    Have your CurrentUserNotifier like so.

    final currentUserProvider = StateNotifierProvider<CurrentUserNotifier, User>((ref) {
      return CurrentUserNotifier();
    });
    
    
    class CurrentUserNotifier extends StateNotifier<User?> {
    
      CurrentUserNotifier() : super(null);
    
      void setUser(User user){
         state = user;
      }
    
      void addUserVehicle(Vehicle vehicle) {
        state =  state..vehicles.add(vehicle);
      }
    
      void removeUserVehicle(int vehicleId) {
        state = state..vehicles.removeWhere((v) => v.id == vehicleId);
      }
    }
    

    Then set the user like so:

    final mainContentFutureProvider= FutureProvider<Map<String, Object>>((ref) async {
        List response = await Future.wait([
           DataController.userInfoDB.getUsers(),
           DataController.userInfoDB.getAnotherList(),
           DataController.userInfoDB.getAnotherList,
        ]);
         
        ref.read(currentUserProvider.notifier).setUser(response[0].first);
    
        return {
          "users": response[0],
          "some_list": response[1],
          "some_list": response[2],
        };
      },
    );
    

    Then you can do:

    press: () async {            
      ref.read(currentUserProvider.notifier).addUserVehicle(vehicle);
    }