5

I copied an example of MVVM with Android Architecture Components, Retrofit, Dagger, and data binding. I am using this code as a starting point to my app in order to start using better architectures in Android app development. However, take these codes:

interface ViewModelInjector {
    /**
     * Injects required dependencies into the specified PostListViewModel.
     * @param postListViewModel PostListViewModel in which to inject the dependencies
     */
    fun inject(postListViewModel: PostListViewModel)

    @Component.Builder
    interface Builder {
        fun build(): ViewModelInjector
        fun networkModule(networkModule: NetworkModule): Builder
    }
}

And

class ViewModelFactory(private val activity: AppCompatActivity) : ViewModelProvider.Factory {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        if (modelClass.isAssignableFrom(PostListViewModel::class.java)) {
            val db = Room.databaseBuilder(
                activity.applicationContext,
                AppDatabase::class.java,
                "posts"
            ).build()

            @Suppress("UNCHECKED_CAST")
            return PostListViewModel(db.postDao()) as T
        }

        throw IllegalArgumentException("Unknown ViewModel class")
    }
}

And

abstract class BaseViewModel : ViewModel() {
    private val injector: ViewModelInjector = DaggerViewModelInjector
        .builder()
        .networkModule(NetworkModule)
        .build()

    init {
        inject()
    }

    private fun inject() {
        when (this) {
            is PostListViewModel -> injector.inject(this)
        }
    }
}

The main problem is that it's stuck with PostListViewModel. I'd like to make it in a dynamic way, accepting any kind of [Name]ViewModel class. I did try some ways using Class<T>, but I no longer have the code. I also tried searching but couldn't come with a good result. Maybe I didn't search for the proper terms. I appreciate any guidance.

1 Answers1

0

Recently I had the same issue and I found a generic ViewModelFactory, it's java but..

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

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

@NonNull
@SuppressWarnings("unchecked")
@Override
public <T extends ViewModel> T create(@NonNull 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);
    }
}
}

I'd put the reference link, but I couldn't find it yet..

Edit:

@Singleton
@Component(modules={ActivityModule.class, FragmentModule.class, AppModule.class})
public interface AppComponents {

@Component.Builder
interface Builder {
    @BindsInstance
    Builder application(Application application);
    AppComponents build();
}

void inject(Weather weatherApp);
}

Now, what is in your interest from the class above should be AppModule.class

@Module(includes = {UserModelModule.class /* other model modules */})
public class AppModule {

// --- DATABASE INJECTION ---

@Provides
@Singleton
YourDatabase provideDatabase(Application application) {
    return Room.databaseBuilder(application,
            YourDatabase.class, "YourDatabase.db")
            //.allowMainThreadQueries() // do NOT DO THIS IN REAL APPLICATIONs
            .fallbackToDestructiveMigration()
            .build();
}

@Provides
@Singleton
UserDao provideUserDao(YourDatabase database) { return database.userDao(); }
.
.
// other Daos that you will have (the above is an example from [https://developer.android.com/jetpack/docs/guide]

Last, I guess you'll want to see how ModelModule class looks like

@Module
public abstract class UserModelModule {

@Binds
@IntoMap
@ViewModelKey(UserViewModel.class)
abstract ViewModel bindUserProfileViewModel(UserViewModel repoViewModel);

@Binds
abstract ViewModelProvider.Factory bindViewModelFactory(ViewModelFactory factory);
}
Ionut J. Bejan
  • 734
  • 11
  • 28
  • Interesting. What about the `databaseBuilder`, should I keep it there? –  Aug 27 '18 at 06:45
  • Help me with it please, I am new to this things and it is very confusing for me, too many codes that do not make sense to me. Where do I put the `Room` database instance? –  Aug 27 '18 at 07:20
  • 1
    Uhm, well.. in this case you should take it easy. Take a look at [this](https://developer.android.com/jetpack/docs/guide) and read it completely – Ionut J. Bejan Aug 27 '18 at 07:21
  • Nice article. Too bad it didn't show anything about `Room.databaseBuilder` –  Aug 27 '18 at 08:21
  • Stil,l it's very difficult in the beginning. I mean, I grasped the idea, but finding good articles that explain a more complete yet simple way to build an app is difficult. The examples I find are always incomplete. –  Aug 27 '18 at 09:07
  • Well, have you tried any of the ways ? To actually get your hands on something ? In this question you posted, you mentioned about generic `ViewModelFactory` but you end up asking more about `Room` database and it's builder :) – Ionut J. Bejan Aug 27 '18 at 11:48
  • Yes, I am trying to follow code examples and I am reading more about it. The Room issue is because the majority of these examples do not use it like that, and refactoring isn't possible because I still do not understand it very well. –  Aug 27 '18 at 13:45