I am currently building an app and I have added Dagger Hilt in order to define a single class to access data. the injection seems working fine but I am not able to store a value in the data class I use.
I have created a Singleton first, which is used by the code to set/get value from a data structure.
#Singleton
class CarListMemorySource #Inject constructor() : CarListInterface {
private var extendedCarList: ExtendedCarList? = null
override fun setListOfVehicles(listOfVehicles: List<item>)
{
extendedCarList?.listOfVehicles = listOfVehicles
}
}
When I am calling setListOfVehicles the listOfVehicules contains 10 items but
The data structure ExtendedCarList is defined as below:
data class ExtendedCarList(
var listOfVehicles: List<item>
)
The Singleton in passed using Hilt like for example in the viewModel below:
#HiltViewModel
class HomeScreenViewModel #Inject constructor(
private val carList: CarListMemorySource
): ViewModel() {
fun getList() {
--> DO SOMETHING TO Get A ListA
carList.setListOfVehicles(ListA)
}
}
And in the activity, using the viewModel, I am just doing this:
#AndroidEntryPoint
class HomeScreenActivity: AppCompatActivity() {
private val viewModel: HomeScreenViewModel by viewModels()
....
viewModel.getList()
....
}
Any idea why and how to fix it ?
Thanks
you never initialize extendedCarList.
extendedCarList?.listOfVehicles = listOfVehicles
above line is exactly the same as
if (extendedCarList != null) extendedCarList.listOfVehicles = listOfVehicles
But it never passes the null check.
I think just changing
private var extendedCarList: ExtendedCarList? = null
to
private val extendedCarList = ExtendedCarList()
might solve it
Related
i have a problem.
I try pass a variable <ArrayList<List>> betwenne two ViewModels.
ViewModel A:
#HiltViewModel
class RouteTrackViewModel #Inject constructor(
application: Application
) : ViewModel(){
private val locationLiveData = LocationLiveData(application)
private var finalRoute = ArrayList<List<Double>>()
private var finalTime : Duration = Duration.ZERO
fun getLocationLiveData() = locationLiveData
fun getFinalRoute() = finalRoute
...
}
ViewModel B:
#HiltViewModel
class RouteSaveViewModel #Inject constructor(
private var routeTrackViewModel : RouteTrackViewModel
) : ViewModel() {
fun getLocationLiveData() = routeTrackViewModel.getLocationLiveData()
fun getRouteLocation() = routeTrackViewModel.getFinalRoute()
}
if i use like that give me this error:
Injection of an #HiltViewModel class is prohibited since it does not create a ViewModel instance correctly.
Access the ViewModel via the Android APIs (e.g. ViewModelProvider) instead.
Injected ViewModel: com.xxxxxxxxxxxxxx.xxxxxxxxxxxx.presentation.routes.route_track.RouteTrackViewModel
please can you help me to fine a good solution?
I expect to have access to final Route in Route Save ViewModel.
I need these values to show on Mapbox a polyline and create a form to save the route later in a JSON file and send it to my database.
In my activity, I have multiple variables being initiated from Intent Extras. As of now I am using ViewModelFactory to pass these variables as arguments to my viewModel.
How do I eliminate the need for ViewModelFacotory with hilt
Here are two variables in my Activity class
class CommentsActivity : AppCompatActivity() {
private lateinit var viewModel: CommentsViewModel
override fun onCreate(savedInstanceState: Bundle?) {
val contentId = intent.getStringExtra(CONTENT_ID_FIELD) //nullable strings
val highlightedCommentId = intent.getStringExtra(HIGHLIGHTED_COMMENT_ID_RF) //nullable strings
val commentsViewModelFactory = CommentsViewModelFactory(
contentId,
highlightedCommentId
)
viewModel = ViewModelProvider(this, commentsViewModelFactory[CommentsViewModel::class.java]
}
}
Here is my viewModel
class CommentsViewMode(
contentId : String?,
highlightedCo;mmentId : String?,
) : ViewModel() {
//logic code here
}
My app is already set up to use hilt but in this case How can I pass these 2 variables and eliminate the viewModelFactory entirely
The trick is to initialize those variables only once, while the activity can be created multiple times. In my apps, I use a flag.
View model:
class CommentsViewModel : ViewModel() {
private var initialized = false
private var contentId : String? = null
private var highlightedCommentId : String? = null
fun initialize(contentId : String?, highlightedCommentId : String?) {
if (!initialized) {
initialized = true
this.contentId = contentId
this.highlightedCommentId = highlightedCommentId
}
}
//logic code here
}
Also, you should know that there is an open issue in dagger project exactly for this capability:
https://github.com/google/dagger/issues/2287
You're welcome to follow the progress.
If you want to use hilt effectively u can follow this steps
Use #HiltViewModel in your view model
#HiltViewModel
class MyViewModel #inject constructor(private val yrParameter): ViewModel {}
Also you no longer need any ViewModelFactory! All is done for you! In your activity or fragment, you can now just use KTX viewModels() directly.
private val viewModel: MyViewModel by viewModels()
Or if you want to use base classes for fragment and activity you can use this code to pass viewModel class
abstract class BaseFragment<V: ViewModel, T: ViewDataBinding>(#LayoutRes val layout: Int, viewModelClass: Class<V>) : Fragment() {
private val mViewModel by lazy {
ViewModelProvider(this).get(viewModelClass)
}
}
I'm trying to call my database (made with Room) from an activity on Android, but it needs the application context, and if I passed "application : Application" in the constructor of my Activity, the build crash and tell me :
java.lang.Class<com.exemple.instabus.PhotoActivity> has no zero argument constructor
Here is my code :
class PhotoActivity(application: Application) : AppCompatActivity() {
private val pictureDao = PictureDatabase.getDatabase(app)
//Some code ....
I need a context, i've tried to pass "this" but i got another error
Can someone give me some help please, I'm a beginner in this technology
EDIT:
Here is my database class, just to show you why I need an Application Context
#Database(entities = [Picture::class], version = 1, exportSchema = false)
abstract class PictureDatabase : RoomDatabase(){
abstract fun pictureDao() : PictureDao
companion object{
#Volatile
private var INSTANCE : PictureDatabase? = null
fun getDatabase(context: Context): PictureDatabase {
if(INSTANCE == null){
synchronized(this){
INSTANCE = Room.databaseBuilder(
context.applicationContext,
PictureDatabase::class.java,
"pictures.db"
).build()
}
}
return INSTANCE!!
}
}
}
Activity is something we do declare in the manifest and then start them using intent, However, the creation of an instance of an activity is done by the system and not by us. An instance is created using constructor, but if it is us then we can have any number of overloaded constructors. But the system needs only one constructor which should be a zero parameter constructor and it should be public.
So your activity signature
class PhotoActivity(application: Application) : AppCompatActivity() {
should be changed to
class PhotoActivity() : AppCompatActivity() {
To call the fun getDatabase(context: Context): PictureDatabase you can pass this from the activity. Activity is an indirect child of Context.
You can do it in the following ways,
private val pictureDao by lazy{ PictureDatabase.getDatabase(this) }
private lateinit var pictureDao:PictureDatabase
then in onCreate() initialize it
final override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.your_layout)
PictureDatabase.getDatabase(this)
//your logic goes here
}
First create a ViewModel, and inside the ViewModel you can access the your Dao, pictureDao.
class PictureViewModel(application: Application) :
AndroidViewModel(application) {
val pictureDao: PictureDao
init {
pictureDao =
PictureDatabase.getDatabase(application).pictureDao()
}
}
Then in your activity, initialize the ViewModel. And access your Dao.
class PhotoActivity : AppCompatActivity() {
private lateinit var pictureViewModel: PictureViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_photo)
pictureViewModel =
ViewModelProviders.of(this).get(PictureViewModel::class.java)
//You can now access your Dao class here:
val pictureDao = pictureViewModel.pictureDao
}
}
You should not pass Application to the constructor. You should pass applicationContext to getDatabase(), like private val pictureDao = PictureDatabase.getDatabase(applicationContext)
I have this class:
class MyViewModel #Inject constructor(repository: MyRepository): ViewModel () {
lateinit var myLiveData: LiveData<User>
fun signIn(credential: AuthCredential) {
myLiveData = repository.signIn(credential)
}
val otherLiveData = repository.signOut() //Works fine
}
The problem is that the repository cannot be used inside the signIn function and I don't know why. However, the second call to signOut works. Can anyone please help?
Creating a class with primary constructor and parameter like this:
class MyViewModel #Inject constructor(repository: MyRepository): ViewModel () { ... }
leads to the fact that repository parameter is not a property, which means it can't be used in other functions of the class. But it can be used in the initializer blocks and in property initializers declared in the class body:
class MyViewModel(repo: MyRepository) {
val repository = repo
}
However, Kotlin has more concise syntax for declaring properties and initializing them from the primary constructor, using val or var keyword:
class MyViewModel #Inject constructor(val repository: MyRepository): ViewModel () { ... }
If you declare parameter with val or var keyword it will be treated as a property and be available in other functions of the class.
More info about constructors and parameters in Kotlin.
Provider
#Module
abstract class AddScriptOrContractFragmentProvider {
#ContributesAndroidInjector(modules = [(AddScriptOrContractFragmentModule::class)])
abstract fun provideAddScriptOrContractFragmentFactory(): AddScriptOrContractFragment
}
Module
#Module
class AddScriptOrContractFragmentModule {
#Provides
fun provideAddScriptOrContractAdapter(context : Context, addScriptOrContractViewModel: AddScriptOrContractViewModel,manageScrip: ManageScrip): AddScriptOrContractAdapter {
return AddScriptOrContractAdapter(
ArrayList<AddScrip>(),
context,
manageScrip, // ManageScrip is Data class unable to get
addScriptOrContractViewModel
)
}
}
Adapter
class AddScriptOrContractAdapter(private val mScripResponseList: MutableList<AddScrip>?,
private val context: Context, private val manageScrip: ManageScrip?,
private val viewModel: AddScriptOrContractViewModel) : RecyclerView.Adapter<BaseViewHolder>() {
}
Fragement
mLayoutManager.orientation = LinearLayoutManager.VERTICAL
mAddScriptAdapter = AddScriptOrContractAdapter(scripList, context!!, manageScrip,addScriptOrContractViewModel)
mAddScriptContractBinding.addScriptRecyclerView.layoutManager = LinearLayoutManager(activity)
mAddScriptContractBinding.addScriptRecyclerView.itemAnimator = DefaultItemAnimator()
mAddScriptContractBinding.addScriptRecyclerView.adapter = mAddScriptAdapter
Error Facing
error: [dagger.android.AndroidInjector.inject(T)] view.marketWatch.manage.ManageScrip
cannot be provided without an #Inject constructor or from an #Provides-annotated method.
What i am trying to do is data binding in adapter with dagger . i want to send a object that is data class to adapter and on that do some business login.
I dont understand what i doing wrong , when i send arraylist() its working fine but when i try to send object or string its facing error please help. Tanx in advance
Like the error states, there are two possible way to provide a dependency:
First one by constructor injection:
data class ManageScrip #Inject constructor(val dependency: Dependency)
Second by implementing it with an annotated method within a module:
#Module
class ProvidingModule {
#Provides
fun provideManageScrip(dependency: Dependency) =
ManageScrip("some", "properties")
}
It all depends on your setup and how you actually have to create the ManageScrip. In general it would be reasonable for data classes to use the second approach.