How can I implement MVVM pattern with riverpod annotation in Flutter?

1.8k Views Asked by At

I am trying to make MVVM architecture with riverpod annotation. And Here are some example code snippets.

import ...

part 'invite_customer_usecase.g.dart';

@riverpod
class InviteCustomerUseCase extends _$InviteCustomerUseCase {
  final ShopRepository _shopRepository;
  final UntactRepository _untactRepository;
  final ManagerRepository _managerRepository;
  final UserRepository _userRepository;

  InviteCustomerUseCase(
      {required ShopRepository shopRepository,
      required UntactRepository untactRepository,
      required ManagerRepository managerRepository,
      required UserRepository userRepository})
      : _shopRepository = shopRepository,
        _untactRepository = untactRepository,
        _managerRepository = managerRepository,
        _userRepository = userRepository;

  @override
  InviteCustomerUseCase build() {
    return InviteCustomerUseCase(
        shopRepository: _shopRepository,
        untactRepository: _untactRepository,
        managerRepository: _managerRepository,
        userRepository: _userRepository);
  }
}
import ...

part 'lobby_viewmodel.g.dart';

@riverpod
class LobbyViewModel extends _$LobbyViewModel {
  final UntactRepository _untactRepository;
  final ShopRepository _shopRepository;
  final ManagerRepository _managerRepository;
  final InviteCustomerUseCase _inviteCustomerUseCase;

  LobbyViewModel(
      {required UntactRepository untactRepository,
      required ShopRepository shopRepository,
      required ManagerRepository managerRepository,
      required InviteCustomerUseCase inviteCustomerUseCase}) :
        _untactRepository = untactRepository,
        _shopRepository = shopRepository,
        _managerRepository = managerRepository,
        _inviteCustomerUseCase = inviteCustomerUseCase;

  @override
  LobbyViewModel build() {
    return LobbyViewModel(
        untactRepository: _untactRepository,
        shopRepository: _shopRepository,
        managerRepository: _managerRepository,
        inviteCustomerUseCase: _inviteCustomerUseCase);
  }
}

And

part 'local_storage_provider.g.dart';

@riverpod
FlutterSecureStorage secureStorage(SecureStorageRef ref) => const FlutterSecureStorage();

@riverpod
ManagerLocalStorage managerLocalStorage(ManagerLocalStorageRef ref) => ManagerLocalStorageImpl(storage: ref.watch(secureStorageProvider));```
part 'repository_provider.g.dart';

@riverpod
ShopRepository shopRepository(ShopRepositoryRef ref) => ShopRepositoryImpl(dio: authDio(), managerLocalStorage: ref.watch(managerLocalStorageProvider));

@riverpod
UntactRepository untactRepository(UntactRepositoryRef ref) => UntactRepositoryImpl(authDio: authDio());

@riverpod
ManagerRepository managerRepository(ManagerRepositoryRef ref) => ManagerRepositoryImpl(localStorage: ref.watch(managerLocalStorageProvider), dataSource: ref.watch(managerDataSourceProvider));

@riverpod
UserRepository userRepository(UserRepositoryRef ref) => UserRepositoryImpl(dataSource: ref.watch(userDataSourceProvider));

At the moment I defined providers like this. But I read and watched videos and it seems like I don't need to define like this. But just @riverpod annotation on the top of class keyword seems like it does the same thing.

My problem is

  @override
  InviteCustomerUseCase build() {
    return InviteCustomerUseCase(
        shopRepository: _shopRepository,
        untactRepository: _untactRepository,
        managerRepository: _managerRepository,
        userRepository: _userRepository);
  }

This part. I think it is a problem?

Some people wrote like this

class LobbyViewModel extends _$LobbyViewModel {
  final UntactRepository _untactRepository = ref.watch(untactRepositoryProvider);
  final ShopRepository _shopRepository = ref.watch(shopRepositoryProvider);
  final ManagerRepository _managerRepository = ref.watch(managerRepositoryProvider);
  final InviteCustomerUseCase _inviteCustomerUseCase = ref.watch(inviteCustomerProvider);

  // No constructor
}

And I think this is a problem because I can't inject when I test. And also, when people use this class other team member won't know if it needs those repositories or usecases.

So, I am trying to use constructor.

They can be singletons. Only ViewModel can be shared in different pages(or screens). which means, sometimes, it needs to be invalidate/refresh or keepAlive when you use pop or push in router.

And of course, generally, way. I'd like to use ref.watch(), ref.read, ... in Widgets. I am using HookConsumerWidget (from flutter_hooks package.)

So, How can I improve my code?

1

There are 1 best solutions below

4
On

Your main problem is that the build method in your classes should return some state of your class. Recall that you have it returning an instance of the class. Take a look at this example - (Async)NotifierProvider.

Then, in the build method of your classes, you can safely watch (use ref.watch and ref.listen) to your shopRepository, UntactRepository and others and quietly get their instances.

In short, try doing something like this:

import ...

part 'lobby_viewmodel.g.dart';

@riverpod
class LobbyViewModel extends _$LobbyViewModel {
  late final UntactRepository _untactRepository;
  late final ShopRepository _shopRepository;
  late final ManagerRepository _managerRepository;
  late final InviteCustomerUseCase _inviteCustomerUseCase;

  // your state can also be of type void
  @override
  MyStateLobby build() {
    _untactRepository = ref.watch(untactRepositoryProvider);
    _untactRepository = ref.watch(shopRepositoryProvider);
    _untactRepository = ref.watch(managerRepositoryProvider);
    _inviteCustomerUseCase = ref.watch(inviteCustomerUseCaseProvider);

    // Get data from these repositories to change your state

    return MyStateLobby(...);
  }
}

Useful links: