How to inject a ViewModel into a composable function using Hilt (Jetpack Compose) - mvvm

I'm doing the same as shown in the documentation here.
I want to Inject the ViewModel into a Composable function (Screen), but I get this error:
Cannot create an instance of class
com.example.blotube.ui.later.LaterViewModel
My ViewModel:
#HiltViewModel
class LaterViewModel #Inject constructor(
private val database: Database
):ViewModel() {
val watchLater=database.videos().getAll()
}
My Composable Function (Screen):
#Composable
fun WatchLater(vm: LaterViewModel = viewModel()){
val videos=vm.watchLater.observeAsState()
val context= LocalContext.current
}

From version androidx.hilt:hilt-navigation-compose:1.0.0-alpha02
you can inject view model into Composable functions by:
hiltViewModel<ViewModelType>()
Example:
#Composable
fun LoginScreen(viewModel: LoginViewModel) {}
LoginScreen(
viewModel = hiltViewModel<LoginViewModel>()
)
Android Developer Documentation compose and hilt
UPDATE:
import androidx.hilt.navigation.compose.hiltViewModel
#Composable
fun LoginScreen(
viewModel: LoginViewModel = hiltViewModel()
){
val videos=vm.watchLater.observeAsState()
val context= LocalContext.current
}

I find the simplest way to do it is inside your composable function.
Add the dependency implementation 'androidx.hilt:hilt-navigation-compose:1.0.0' then;
#Composable
fun Foo(){
val viewModel : Bar = hiltViewModel()
}
then you can use the viewmodel as usual.

You can use ViewModel directly inside Composable function via hiltViewModel()
#Composable
fun WatchLater(vm: LaterViewModel = hiltViewModel()) {
val videos = vm.watchLater.observeAsState()
val context = LocalContext.current
}
Please make sure to add following
Adding androidx.hilt:hilt-navigation-compose dependency to your module level Gradle file. Do check for latest version(tested on 1.0.0-alpha03).
#HiltViewModel to your ViewModel.
#AndroidEntryPoint for the owner using the Composable function.

This appears to be a bug in Jetpack Compose, will probably need to wait for an update on the Jetpack libraries to address it.
As a possible workaround, you could instantiate the viewmodel in your activity and pass it to your composable function
val viewModel: LaterViewModel = viewModel(
"later_viewmodel",
factory = defaultViewModelProviderFactory
)
WatchLater(viewModel)
if you are using the Nav graph component you can also scope your viewmodel to the nav graph using
val viewModel: LaterViewModel = hiltNavGraphViewModel<LaterViewModel>()
WatchLater(viewModel)

base document Inject Hilt In Composable Function
Note: notice to import class
Sample you can used viewModel()
Example:
...
import androidx.lifecycle.viewmodel.compose.viewModel
...
#Composable
fun MyScreen(
viewModel: MyViewModel = viewModel()
) { /* ... */ }
Full Example:
...
import androidx.lifecycle.viewmodel.compose.viewModel
...
#HiltViewModel
class MyViewModel #Inject constructor(
private val savedStateHandle: SavedStateHandle,
private val repository: ExampleRepository
) : ViewModel() { /* ... */ }
#Composable
fun MyScreen(
viewModel: MyViewModel = viewModel()
) { /* ... */ }

Related

How to add paramaters to new livedata builder in the view model

I'm trying to convert my view models to use the new livedata builder from live data2.0
In all the examples i've seen when you use this new builder pattern they omit how to set parameters take this view model for example, userId is not defined
class UserViewModel : ViewModel() {
private val repository = UserRepository()
val user: LiveData<Response<User>> = liveData {
val data = repository.getUser(userId) // loadUser is a suspend function.
emit(data)
}
}
looks pretty clean on concise, but where do I set the userId its not a function.
before i would expose a function that took a paramater then would update the livedata property.
I was thinking of something like this
class UserViewModel : ViewModel() {
private val repository = UserRepository()
var userId : String? = null
val user: LiveData<Response<User>> = liveData {
val data = repository.getUser(userId) // loadUser is a suspend function.
emit(data)
}
}
and the fragment can set the id, but what if I change the id and want to make another network call?

Adding Multiple Adapter Into Fragment

Hello all can you advice me how to add multiple different adapter in one fragment using dagger ?
#Module
class HomeFragmentModule {
#Provides
internal fun provideHomeInteractor(interactor: HomeInteractor): HomeMVPInteractor = interactor
#Provides
internal fun provideRegisterPresenter(presenter: HomePresenter<HomeView, HomeMVPInteractor>) : HomeMVPPresenter<HomeView, HomeMVPInteractor> = presenter
#Provides
#Named("ppobAdapter")
fun providePpobAdapter(): PpobAdapter = PpobAdapter(ArrayList())
#Provides
#Named("sliderAdapter")
internal fun provideSliderAdapter(): SliderAdapter = SliderAdapter(ArrayList())
} ```
Try this:
HomeFragment : Fragment() {
#Inject
#Named("ppobAdapter")
lateinit var ppobAdapter: PpobAdapter
#Inject
#Name("sliderAdapter")
lateinit var sliderAdapter: SliderAdapter
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
DaggerAppComponent
.builder()
.build()
.inject(this)
}
}
With a component that looks like this:
#Component(modules = [HomeFragmentModule::class])
interface AppComponent {
fun inject(fragment: HomeFragment)
}
For a really good intro to dagger I always advise this tutorial series

Reactive repository does not save an object

I think I don't understand well how a Reactive repository and handlers using it work. I have written a special test class only to test the simpliest handler using a repository
#SpringBootTest
class TestRepository() {
#Autowired
lateinit var myRepo: myRepo
#Autowired
lateinit var myHandler: MyHandler
#Test
fun `save with a handler`() {
val myObject = MyObject()
myHandler.save(request).subscribe()
StepVerifier.create (myRepository.count() ) <--this does not work
.expectNext (1L )
.expectComplete().verify()
}
#Test
fun `test only database saving`() {
val object = MyObject()
myRepo.save(myRepo).subscribe()
StepVerifier.create (myRepo.count() ) <-- this works
.expectNext (1L )
.expectComplete().verify()
}
}
my handler and repository are defined in the following way:
#Service
class MyHandler(private val myRepository: MyRepository) {
fun save(object: MyObject): Mono<MyObject> {
return myRepository.save(request)
}
}
#Repository
interface MyRepo : ReactiveMongoRepository<MyObject, String> {
fun save(request: MyObject): Mono<MyObject>
}
I also tried to play with subscribe method but it still does not see the results.
What should I correct?
Use Mono.then function to chain save and count functions and get a resulting Mono:
#Test
fun `save with a handler`() {
val countAfterSave = myHandler.save(MyObject()).then(myRepository.count());
StepVerifier.create(countAfterSave)
.expectNext(1L)
.expectComplete()
.verify()
}

MVVM without DataBinding

I started learning Kotlin to develop Android applications and for my first project I have to use MVVM as pattern to separate the presentation layer from the logic. All the articles I've found about MVVM use DataBinding library to bind the data of the models directly to the xml views. I worked a little with DataBinding in Java but I doesn't like it because it is very difficult to find errors when binding is wrong. My questions is if there is another way to use MVVM without DataBinding?
Sure it is. Just abstract example: imagine ViewModel:
class SomeViewModel : ViewModel() {
private lateinit var roadmap: RoleScreenRoadmap
private lateinit var uiScope: CoroutineScope
private val _nameRequired = MutableLiveData<Boolean>()
private val _userName = MutableLiveData<String>()
val nameRequired: LiveData<Boolean>
get() = _nameRequired
val userName: LiveData<String>
get() = _userName
...
}
And now Activity:
class AbstractActivity : AppCompatActivity() {
private lateinit var viewModel: RoleScreenViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_some)
initializeDependencies()
performBindings()
}
private fun initializeDependencies() {
viewModel = ViewModelProviders.of(this).get(AbstractViewModel::class.java)
}
private fun performBindings() {
val lifecycle = ::getLifecycle
viewModel.nameRequired.observe(lifecycle) { nameRequired: Boolean? ->
if (nameRequired!!) {
showNameInputWindow()
}
}
viewModel.userName.observe(lifecycle, ::setTitle)
}
...
}
You can just subscribe during android's lifecycle component initialization and no databinding is ever needed.

I can't reach any class member from a nested class in Kotlin

I want to access a member of the MainFragment class from PersonAdapter class but none of them are available. I tried making both the classes and the members public and private also but so far nothing worked.
I guess I'm missing something obvious but I just can't figure it out.
class MainFragment : Fragment() {
lateinit var personAdapter: PersonAdapter
lateinit var personListener: OnPersonSelected
private var realm: Realm by Delegates.notNull()
lateinit var realmListener: RealmChangeListener<Realm>
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val v = inflater.inflate(R.layout.fragment_main, container, false)
return v
}
class PersonAdapter() : RecyclerView.Adapter<ViewHolder>() {
var localPersonList = personList
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bindItems(localPersonList[position])
holder.itemView.setOnClickListener {
Toast.makeText(context, "click", Toast.LENGTH_SHORT).show()
//I want to reach personListener from here
}
}
override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): ViewHolder {
val v = LayoutInflater.from(parent!!.context).inflate(R.layout.person_list_item, parent, false)
return ViewHolder(v)
}
}}
In Kotlin, nested classes cannot access the outer class instance by default, just like nested static classes can't in Java.
To do that, add the inner modifier to the nested class:
class MainFragment : Fragment() {
// ...
inner class PersonAdapter() : RecyclerView.Adapter<ViewHolder>() {
// ...
}
}
Note that an inner class holds a reference to its containing class instance, which may affect the lifetime of the latter and potentially lead to a memory leak if the inner class instance is stored globally.
See: Nested classes in the language reference
In Kotlin, there are 2 types of the nested classes.
Nested Class
inner Class
Nested class are not allowed to access the member of the outer class.
If you want to access the member of outer class in the nested class then you need to define that nested class as inner class.
class OuterClass{
var name="john"
inner class InnerClass{
//....
}
}
Add inner
Note that Android Studio's Code completion(IntelliSense) doesn't work right inside the inner class
class OuterClass {
val outerVariable = "Hello, World!"
inner class InnerClass {
// Code completion doesn't work here
val innerVariable = outerVariable // Code completion work
fun innerFunction() {
// Code completion work
}
}
}