web-dev-qa-db-fra.com

Comment créer ViewModel et y injecter un référentiel avec dagger 2?

J'essaie de comprendre ViewModel. Je crée ViewModel:

public class UsersViewModel extends ViewModel {

    private final UsersRepository usersRepository;

    public UsersViewModel(UsersRepository usersRepository) {
        this.usersRepository = usersRepository;
    }

    public LiveData<List<User>> loadAll() {
        return usersRepository.getAll();
    }

}

Mais je ne comprends pas 2 choses:

  1. Comment puis-je injecter UsersRepository à cette VievModel? Quand j'ai utilisé le présentateur, je peux le créer avec le poignard 2 comme ceci:
@Module
public class PresentersModule {

    @Singleton
    @Provides
    UsersPresenter provideUsersPresenter(UsersRepository usersRepository) {
        return new UsersPresenter(usersRepository);
    }
}

mais comment puis-je le faire avec ViewModel? Comme ça? 

@Module
public class ViewModelsModule {

    @Singleton
    @Provides
    UsersViewModel provideUsersViewModel(UsersRepository usersRepository) {
        return new UsersViewModel(usersRepository);
    }
}
  1. Comment puis-je obtenir ce ViewModel en fragment? Avec le présentateur, je peux ceci:

    presenter = MyApplication.get (). getAppComponent (). getUsersPresenter ();

5
ip696

ViewModel est créé via ViewModelProvider qui utilise ViewModelFactory pour créer les instances. Vous ne pouvez pas injecter ViewModels directement, vous devriez plutôt utiliser une fabrique personnalisée comme ci-dessous

@Singleton
public class DaggerViewModelFactory implements ViewModelProvider.Factory {
private final Map<Class<? extends ViewModel>, Provider<ViewModel>> creators;

@Inject
public DaggerViewModelFactory(Map<Class<? extends ViewModel>, Provider<ViewModel>> creators) {
    this.creators = creators;
}

@SuppressWarnings("unchecked")
@Override
public <T extends ViewModel> T create(Class<T> modelClass) {
    Provider<? extends ViewModel> creator = creators.get(modelClass);
    if (creator == null) {
        for (Map.Entry<Class<? extends ViewModel>, Provider<ViewModel>> entry : creators.entrySet()) {
            if (modelClass.isAssignableFrom(entry.getKey())) {
                creator = entry.getValue();
                break;
            }
        }
    }
    if (creator == null) {
        throw new IllegalArgumentException("unknown model class " + modelClass);
    }
    try {
        return (T) creator.get();
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}
}

Ensuite, vous avez besoin d'un module pour le poignard qui crée la fabrique de modèles de vues et les modèles de vues eux-mêmes. 

@Module
abstract class ViewModelModule {
@Binds
abstract ViewModelProvider.Factory bindViewModelFactory(DaggerViewModelFactory factory);

@Binds
@IntoMap
@ViewModelKey(VideoListViewModel.class)
abstract ViewModel provideVideoListViewModel(VideoListViewModel videoListViewModel);

@Binds
@IntoMap
@ViewModelKey(PlayerViewModel.class)
abstract ViewModel providePlayerViewModel(PlayerViewModel playerViewModel);

@Binds
@IntoMap
@ViewModelKey(PlaylistViewModel.class)
abstract ViewModel providePlaylistViewModel(PlaylistViewModel playlistViewModel);

@Binds
@IntoMap
@ViewModelKey(PlaylistDetailViewModel.class)
abstract ViewModel providePlaylistDetailViewModel(PlaylistDetailViewModel playlistDetailViewModel);
}

Le fichier ViewModelKey est comme ça

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@MapKey
@interface ViewModelKey {
    Class<? extends ViewModel> value();
}

Maintenant, pour obtenir votre modèle de vue dans l'activité ou le fragment, injectez simplement la fabrique de modèles de vues, puis utilisez-la pour créer les instances de modèles de vues.

public class PlayerActivity extends BaseActivity {
     @Inject DaggerViewModelFactory viewModelFactory;
     PlayerViewModel viewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        AndroidInjection.inject(this);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_player);
        viewModel = ViewModelProviders.of(this,viewModelFactory).get(PlayerViewModel.class);

}

Pour injecter quelque chose dans votre ViewModel, tel que le référentiel, utilisez simplement Injection de constructeur.

public class PlayerViewModel extends ViewModel {
    private VideoRepository videoRepository;
    private AudioManager audioManager;


    @Inject
    public PlayerViewModel(VideoRepository videoRepository, AudioManager audioManager) {
         this.videoRepository = videoRepository;
         this.audioManager = audioManager;

    }
}

Découvrez l'exemple entièrement fonctionnel à partir d'ici https://github.com/alzahm/VideoPlayer , J'ai aussi appris beaucoup de choses sur les poignards à partir d'échantillons Google, vous pouvez également les consulter. 

6
Reza

J'ai écrit une bibliothèque qui devrait rendre cela plus simple et plus clair, sans multibindings ni embase standard, tout en donnant la possibilité de paramétrer davantage la ViewModel à l'exécution: https://github.com/radutopor/ViewModelFactory

@ViewModelFactory
class UserViewModel(@Provided repository: Repository, userId: Int) : ViewModel() {

    val greeting = MutableLiveData<String>()

    init {
        val user = repository.getUser(userId)
        greeting.value = "Hello, $user.name"
    }    
}

Dans la vue:

class UserActivity : AppCompatActivity() {
    @Inject
    lateinit var userViewModelFactory2: UserViewModelFactory2

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_user)
        appComponent.inject(this)

        val userId = intent.getIntExtra("USER_ID", -1)
        val viewModel = ViewModelProviders.of(this, userViewModelFactory2.create(userId))
            .get(UserViewModel::class.Java)

        viewModel.greeting.observe(this, Observer { greetingText ->
            greetingTextView.text = greetingText
        })
    }
}
0
Radu Topor