Внедрить свойство в ViewModel с помощью Dagger 2 - PullRequest
0 голосов
/ 24 января 2019

Я пытаюсь научиться использовать Dagger 2. Пожалуйста, помогите с исключением:

Исключение: UninitializedPropertyAccessException: свойства свойства lateinit не имеют был инициализирован

MainActivityViewModel:

class MainActivityViewModel : ViewModel() {
    private lateinit var tripsLiveData: MutableLiveData<List<Trip>>

    @Inject
    lateinit var trips : List<Trip>

    fun getTrips() : LiveData<List<Trip>> {
        if (!::tripsLiveData.isInitialized){
            tripsLiveData = MutableLiveData()
            tripsLiveData.value = trips
        }
        return tripsLiveData
    }
}

TripModule:

@Module
class TripModule{
    @Provides
    fun provideTrips(): List<Trip> {

        var list = ArrayList<Trip>()
        list.add(Trip(100,10))
        list.add(Trip(200,20))
        return list
    }
}

AppComponent:

@Singleton
@Component(modules = [
    AndroidSupportInjectionModule::class,
    ActivityBuilder::class,
    TripModule::class])
interface AppComponent{
    @Component.Builder
    interface Builder {
        @BindsInstance
        fun application(application: Application): Builder

        fun build(): AppComponent
    }

    fun inject(app: MyApplication)
}

MainActivity:

class MainActivity : AppCompatActivity() {

    @Inject
    lateinit var tripsAdapter: TripsAdapter

    override fun onCreate(savedInstanceState: Bundle?) {

        // Inject external dependencies
        AndroidInjection.inject(this)

        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        setupRecyclerView();
        setUpViewModel();
    }

    private fun setupRecyclerView() {
        recycler_view.apply {
            layoutManager = LinearLayoutManager(context)
            adapter = tripsAdapter
        }
    }

    private fun setUpViewModel(){
        val model = ViewModelProviders.of(this).get(MainActivityViewModel::class.java)
        model.getTrips().observe(this, Observer { tripsAdapter.trips = it!! })
    }
}

Ответы [ 2 ]

0 голосов
/ 24 января 2019

Если вы хотите, чтобы ваши view-модели были частью графа кинжалов, вам нужно сделать несколько вещей - используя мульти-привязки кинжала (только один раз, для более новых view-моделей это будет проще). Вы создадите новую фабрику viewmodel, которая позаботится о создании экземпляров viewmodels. Эта фабрика будет частью графика кинжала и, следовательно, будет иметь ссылки на все, что предоставляется через кинжал. Затем вы можете использовать инжектор конструктора через @Inject constructor(anyParameterFromDagger: Param) или @Inject lateinit var someParam: Param внутри тела viewmodel.

1) Создать классификатор для классов моделей представления

@MustBeDocumented
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
@MapKey
annotation class ViewModelKey(val value: KClass<out ViewModel>)

2) создать фабрику моделей представления, которая получает значения из мультибиндингов кинжала

@Singleton
class DaggerViewModelFactory @Inject constructor(
    private val creators: Map<Class<out ViewModel>, @JvmSuppressWildcards Provider<ViewModel>>
) : ViewModelProvider.Factory {

    @Suppress("UNCHECKED_CAST")
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        var creator: Provider<out ViewModel>? = creators[modelClass]
        if (creator == null) {
            for ((key, value) in creators) {
                if (modelClass.isAssignableFrom(key)) {
                    creator = value
                    break
                }
            }
        }
        if (creator == null) {
            throw IllegalArgumentException("unknown model class $modelClass")
        }
        try {
            return creator.get() as T
        } catch (e: Exception) {
            throw RuntimeException(e)
        }
    }
}

3) иметь модуль кинжала, который предоставит завод (из пункта 2), а затем ваши модели зрения

abstract class YourDaggerModuleWhichThenNeedToBePartOfYourGraphAsIncluded {

    @Binds
    abstract fun bindViewModelFactory(factory: DaggerViewModelFactory): ViewModelProvider.Factory // this needs to be only one for whole app (therefore marked as `@Singleton`)

    @Binds
    @IntoMap
    @ViewModelKey(MainActivityViewModel::class)
    abstract fun bindMainActivityViewModel(vm: MainActivityViewModel): ViewModel // for every viewmodel you have in your app, you need to bind them to dagger
}

4) в своей деятельности, когда вы получаете свою модель представления, вам нужно использовать фабрику из кинжала: (места, помеченные как // TODO в коде ниже)

class MainActivity : AppCompatActivity() {

    @Inject
    lateinit var tripsAdapter: TripsAdapter

    @Inject
    lateinit var viewModelFactory: ViewModelProvider.Factory // TODO this was added to the activity

    override fun onCreate(savedInstanceState: Bundle?) {

        // Inject external dependencies
        AndroidInjection.inject(this)

        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        setupRecyclerView();
        setUpViewModel();
    }

    private fun setupRecyclerView() {
        recycler_view.apply {
            layoutManager = LinearLayoutManager(context)
            adapter = tripsAdapter
        }
    }

    private fun setUpViewModel(){
        val model = ViewModelProviders.of(this, viewModelFactory)[MainActivityViewModel::class.java] // TODO this was changed

        model.getTrips().observe(this, Observer { tripsAdapter.trips = it!! })
    }
}

Я не предоставил код для включения модуля в компонент кинжала, так как я надеюсь, что это то, что вы уже сделали.

Вы можете узнать больше об этом, например. в этой средней статье (я не автор статьи):

0 голосов
/ 24 января 2019
@Provides
fun provideTrips(): List<Trip> {

Я немного настороженно отношусь к этому в том смысле, что сомневаюсь, что действительно работа Даггера - предоставить это непосредственно вам, но я просто приму это как должное и проигнорирую этона данный момент.


Ваш код должен быть:

class MainActivityViewModel @Inject constructor(
    trips: List<Trip>
): ViewModel() {
    private val tripsLiveData: MutableLiveData<List<Trip>> = MutableLiveData()

    init {
        tripsLiveData.setValue(trips)
    }

    fun getTrips() : LiveData<List<Trip>> = tripsLiveData
}

@Module
class TripModule{
    @Provides
    // @Singleton // <-- possibly should be here?
    fun provideTrips(): List<Trip> = listOf(
        Trip(100,10),
        Trip(200,20)
    )
}

@Singleton
@Component(modules = [
    AndroidSupportInjectionModule::class,
    ActivityBuilder::class,
    TripModule::class
])
interface AppComponent{
    @Component.Builder
    interface Builder {
        @BindsInstance
        fun application(application: Application): Builder

        fun build(): AppComponent
    }

    fun inject(app: MyApplication)
}

class MainActivity : AppCompatActivity() {
    @Inject
    lateinit var viewModelProvider: Provider<MainActivityViewModel>

    // this is specifically not marked with `@Inject`
    lateinit var viewModel: MainActivityViewModel

    private val tripsAdapter = TripsAdapter()

    override fun onCreate(savedInstanceState: Bundle?) {
        // Inject external dependencies
        AndroidInjection.inject(this)

        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        setupRecyclerView();
        setUpViewModel();
    }

    private fun setupRecyclerView() {
        recycler_view.apply {
            layoutManager = LinearLayoutManager(context)
            adapter = tripsAdapter
        }
    }

    private fun setUpViewModel(){
        viewModel = ViewModelProviders.of(this, object: ViewModelProvider.Factory {
            override fun <T : ViewModel?> create(modelClass: Class<T>): T {
                if(modelClass == MainActivityViewModel::class.java) {
                    @Suppress("UNCHECKED_CAST")
                    return viewModelProvider.get() as T
                }
                throw IllegalArgumentException("Unexpected argument: $modelClass")
            }
        }).get(MainActivityViewModel::class.java)

        viewModel.getTrips().observe(this, Observer { 
            val trips = it ?: return@observe
            tripsAdapter.trips = trips 
        })
    }
}

Ака, вы должны использовать инжектор конструктораиспользуйте Provider<T>, чтобы получить ViewModel от Dagger только тогда, когда он вам действительно нужен, и в противном случае инициализируйте его с помощью ViewModelProviders.Factory, так что вы фактически получите ViewModel из Dagger.

...