How to instantiate a AdnroidViewModel in Linear Layout - kotlin

I am creating a Custom Linear Layout view to represent a custom calender. Ealier I used SQlitehelper to get the data. But now I am migrating to Room DB. But I couldn't instantiate AndroidViewModel and get data.
This is the AndroidViewModel
class AdminExpensesVM(application: Application):AndroidViewModel(application) {
private val repository:AdminExpensesRepo
val readAll :LiveData<List<AdminExpensesData>>
init {
val adminExpensesDB=AdminExpensesDatabase.getInstance(application).adminExpensesDao
repository= AdminExpensesRepo(adminExpensesDB)
readAll=repository.getAllExpenses()
}
}
This is the Custom LinearLayout
class Admin_Calender : LinearLayout {
private lateinit var adminExpensesVM: AdminExpensesVM;
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
val view: View = inflater.inflate(R.layout.admin_calender, this)
adminExpensesVM=ViewModelProvider.AndroidViewModelFactory.getInstance(context as Application).create(AdminExpensesVM::class.java)
}
}
But this cannot be done becasue it get a error casting context to application. How can I do this? Is there anyway to get application to instantiate AndroidViewModel or is there any way to do it with only context?
Thank you

I don't really get your question but maybe this can help try to make it scoped with your activity
val adminExpensesVM: AdminExpensesVM by activityViewModels()

Related

Kotlin on Android: How to use LiveData from a database in a fragment?

I use MVVM and have a list of data elements in a database that is mapped through a DAO and repository to ViewModel functions.
Now, my problem is rather banal; I just want to use the data in fragment variables, but I get a type mismatch.
The MVVM introduces a bit of code, and for completeness of context I'll run through it, but I'll strip it to the essentials:
The data elements are of a data class, "Objects":
#Entity(tableName = "objects")
data class Objects(
#ColumnInfo(name = "object_name")
var objectName: String
) {
#PrimaryKey(autoGenerate = true)
var id: Int? = null
}
In ObjectsDao.kt:
#Dao
interface ObjectsDao {
#Query("SELECT * FROM objects")
fun getObjects(): LiveData<List<Objects>>
}
My database:
#Database(
entities = [Objects::class],
version = 1
)
abstract class ObjectsDatabase: RoomDatabase() {
abstract fun getObjectsDao(): ObjectsDao
companion object {
// create database
}
}
In ObjectsRepository.kt:
class ObjectsRepository (private val db: ObjectsDatabase) {
fun getObjects() = db.getObjectsDao().getObjects()
}
In ObjectsViewModel.kt:
class ObjectsViewModel(private val repository: ObjectsRepository): ViewModel() {
fun getObjects() = repository.getObjects()
}
In ObjectsFragment.kt:
class ObjectsFragment : Fragment(), KodeinAware {
private lateinit var viewModel: ObjectsViewModel
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(this, factory).get(ObjectsViewModel::class.java)
// I use the objects in a recyclerview; rvObjectList
rvObjectList.layoutManager = GridLayoutManager(context, gridColumns)
val adapter = ObjectsAdapter(listOf(), viewModel)
// And I use an observer to keep the recyclerview updated
viewModel.getObjects.observe(viewLifecycleOwner, {
adapter.objects = it
adapter.notifyDataSetChanged()
})
}
}
The adapter:
class ObjectsAdapter(var objects: List<Objects>,
private val viewModel: ObjectsViewModel):
RecyclerView.Adapter<ObjectsAdapter.ObjectsViewHolder>() {
// Just a recyclerview adapter
}
Now, all the above works fine - but my problem is that I don't want to use the observer to populate the recyclerview; in the database I store some objects, but there are more objects that I don't want to store.
So, I try to do this instead (in the ObjectsFragment):
var otherObjects: List<Objects>
// ...
if (condition) {
adapter.objects = viewModel.getObjects()
} else {
adapter.objects = otherObjects
}
adapter.notifyDataSetChanged()
And, finally, my problem; I get type mismatch for the true condition assignment:
Type mismatch: inferred type is LiveData<List> but List was expected
I am unable to get my head around this. Isn't this pretty much what is happening in the observer? I know about backing properties, such as explained here, but I don't know how to do that when my data is not defined in the ViewModel.
We need something to switch data source. We pass switching data source event to viewModel.
mySwitch.setOnCheckedChangeListener { _, isChecked ->
viewModel.switchDataSource(isChecked)
}
In viewModel we handle switching data source
(To use switchMap include implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.4.0")
class ObjectsViewModel(private val repository: ObjectsRepository) : ViewModel() {
// Best practice is to keep your data in viewModel. And it is useful for us in this case too.
private val otherObjects = listOf<Objects>()
private val _loadDataFromDataBase = MutableLiveData<Boolean>()
// In case your repository returns liveData of favorite list
// from dataBase replace MutableLiveData(otherObjects) with repository.getFavorite()
fun getObjects() = _loadDataFromDataBase.switchMap {
if (it) repository.getObjects() else MutableLiveData(otherObjects)
}
fun switchDataSource(fromDataBase: Boolean) {
_loadDataFromDataBase.value = fromDataBase
}
}
In activity/fragment observe getObjects()
viewModel.getObjects.observe(viewLifecycleOwner, {
adapter.objects = it
adapter.notifyDataSetChanged()
})
You can do something like this:
var displayDataFromDatabase = true // Choose whatever default fits your use-case
var databaseList = emptyList<Objects>() // List we get from database
val otherList = // The other list that you want to show
toggleSwitch.setOnCheckedChangeListener { _, isChecked ->
displayDataFromDatabase = isChecked // Or the negation of this
// Update adapter to use databaseList or otherList depending upon "isChecked"
}
viewModel.getObjects.observe(viewLifecycleOwner) { list ->
databaseList = list
if(displayDataFromDatabase)
// Update adapter to use this databaseList
}

How can I access to data class object from activity?

I am trying to access to data class (Content)and I would like to use object(val isSelected: Boolean?)from PictureActivity. However, it causes UninitializedPropertyAccessException: lateinit property content has not been initialized. Do you know how to solve this situation? I used lateinit but I don't even know if using lateinit is the best way to access to data class(Content). If you know other way to access to it, please let me know.
The code is down below.
Content.kt
data class Content(
val id: Int,
val text: String,
val isSelected: Boolean?,
val url: String?
)
PictureActivity.kt
class PictureActivity : BaseActivity() {
private lateinit var binding: PictureActivityBinding
private lateinit var content: Content
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = PictureActivityBinding.inflate(layoutInflater)
setContentView(binding.root)
if(content.isSelected!!){
binding.button1.setOnClickListner{
startContentDownload(content.url!!)
return#setOnClickListener
}
}
private fun startContentDownload(url: String) {
//download image
}
}
}
lateinit keyword in Kotlin gives you an option to initialize it later but make sure you do it before you use.
To check if that variable is initialized or not, you can use below:
if(::content.isInitialized) {
// put your code here
}
In your case you have get data from somewhere(network call maybe) to fill in content data class, and then you will be able to use it.
You need to initialize the content variable first then only you can use it
content = Content(...)

BiometricPrompt with Jetpack Compose

BiometricPrompt requires either Fragment or FragmentActivity in its constructor. I cannot find out how to use BiometricPrompt from a Composable screen, not in the documentation, not in any tutorial. Has anyone in here dealt with the same problem? Or is there any other way to use biometric authentication in a fully Compose built application?
Subclass your MainActivity from FragmentActivity, then in composable get your context:
val context = LocalContext.current as FragmentActivity
Check out some examples on github: https://github.com/search?l=kotlin&q=BiometricPrompt%20composable&type=Code
Ok, it was quite simple in the end, but has taken me hours, so here is the answer for anyone struggling with this.
Make sure, that your MainActivity inherits from FragmentActivity(). Then you will be able to cast LocalContext.current to FragmentActivity.
val context = LocalContext.current as FragmentActivity
val biometricPrompt = BiometricPrompt(
context,
authenticationCallback
)
replace ComponetActivity() with FragmentActivity() then use normal compose view from FragmentActivity()
setContent {
FingerPrintAppTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background
) {
Greeting("Android")
}
}
}
then everything works fine
For those that cannot change their activity base class, there is issuetracker.google.com/issues/178855209 to request for a biometric-compose artifact. Unfortunately, as of version 1.2.0-alpha04 no work has been done towards it.
You can inherit from AppCompatActivity, which inherits from FragmentActivity, which inherits from ComponentActivity.
Then you can do:
inline fun <reified Activity : FragmentActivity> Context.getActivity(): Activity? {
return when (this) {
is Activity -> this
else -> {
var context = this
while (context is ContextWrapper) {
context = context.baseContext
if (context is Activity) return context
}
null
}
}
}
and then:
val activity = LocalContext.current.getActivity<MainActivity>()
val biometricPrompt = BiometricPrompt(
activity,
authenticationCallback
)

Kotlin Dagger 2 Sharing ViewModel between Activity and Fragment

As mentioned in the title I've got a problem with Dagger 2 injection. I have a single activity and multiple fragments. I'm trying to share activity's ViewModel with every child fragment. I based my solution on Google's Android Architecture ToDo sample. I've created ViewModelFactory as
ViewModelFactory
If you jump to the link you will see that in their solution there's a separate ViewModel for every activity and fragment. They are not showing how to deal with scenarios like mine. My implementation of ActivityModule looks like:
#Module
abstract class SampleModule {
#ContributesAndroidInjector(
modules = [
ViewModelBuilder::class
]
)
internal abstract fun sampleActivity(): SampleActivity
#Binds
#IntoMap
#ViewModelKey(SampleViewModel::class)
abstract fun bindViewModel(viewModel: SampleViewModel): ViewModel
}
My activity extends DaggerAppCompatActivity and fragment DaggerFragment and as follows my injection of view model looks simple as
class SampleActivity : DaggerAppCompatActivity() {
#Inject
lateinit var viewModel: SampleViewModel
...
I can't find a web solution to my problem. I'm a pretty novice user of Dagger. I've tried to implement Subcomponent but still, it's not working because all the examples I have searched so far didn't use DaggerApplication, DaggerAppCompatActivity and my way of injection. Please suggest any solution or if a subcomponent way is right please show me how to do it if it's possible in my current architecture.
Thank you very much in advance.
#silaros88 I was facing yor same issue, share a ViewModel between multiple fragmnets in a single Activity application, and i solved playing with the ViewmModelStoreOwner.
Steps to fix your issue.
See here TasksFragment.kt how they inject the ViewModelProvider.Factory instead of the ViewModel
Retrieve the desire ViewModel using one of this two options:
viewModels<SharedDesireViewModel> (requireActivity()) { Injected ViewModelProvider.Factory }
ViewModelProvider(requireActivity(), Injected ViewModelProvider.Factory ).get(SharedDesireViewModel::class.java)
Examples:
Option #1:
FragmentA.kt
class FragmentA: DaggerFragment() {
#Inject
lateinit var viewModelProviderFactory: ViewModelProvider.Factory
private val mainViewModel: MainViewModel by viewModels({requireActivity()}) { viewModelProviderFactory }
......
FragmentB.kt
class FragmentB: DaggerFragment() {
#Inject
lateinit var viewModelProviderFactory: ViewModelProvider.Factory
private val mainViewModel: MainViewModel by viewModels({requireActivity()}) { viewModelProviderFactory }
......
Option #2:
FragmentA.kt
class FragmentA: DaggerFragment() {
#Inject
lateinit var viewModelProviderFactory: ViewModelProvider.Factory
private val mainViewModel: MainViewModel by lazy {
ViewModelProvider(requireActivity(), viewModelProviderFactory)
.get(MainViewModel::class.java)
}
......
FragmentB.kt
class FragmentB: DaggerFragment() {
#Inject
lateinit var viewModelProviderFactory: ViewModelProvider.Factory
private val mainViewModel: MainViewModel by lazy {
ViewModelProvider(requireActivity(), viewModelProviderFactory)
.get(MainViewModel::class.java)
}
......
I solved the problem with a slightly different approach.
Since the fragments and the activity both have dagger modules.
In the ActivityModule I am providing the sharedViemodel as below
#Module
class ActivityModule(private val activity: AppCompatActivity)
{
#Provides
fun provideMainSharedVieModel() : MainSharedViewModel =
ViewModelProvider(activity).get(MainSharedViewModel::class.java)
}
And in my fragment module I am again providing the same viemodel as below:
#Module
class FragmentModule(private val fragment: Fragment)
{
#Provides
fun provideMainSharedVieModel() : MainSharedViewModel =
ViewModelProvider(fragment.activity!!).get(MainSharedViewModel::class.java)
}
Since the ViewModels are stored in a map with the activity or fragments as the key, hence providing the sharedViewModel with "fragment.activity!!" in the Fragment module will not create a new instance of the viewmodel , it will just the return the already instantiated shared viewmodel to the fragment.

Koin injecting into WorkManager

I have a basic work manager
class BackgroundSyncWorker (
appContext: Context,
workerParams: WorkerParameters
): Worker(appContext, workerParams) {
override fun doWork(): Result {
return Result.success()
}
}
And I want to inject my repository into this to do some work in my database. I've set Koin up correctly but can't seem to find a way of how to inject my dependency into the Worker. I've tried inheriting the KoinComponent and trying to do it using that, but by inject() doesn't exist, but there's two by inject methods that I can't find how to use. There doesn't seem to be any information on how to inject into managers, although there's a few for using dagger.
This does actually work, I was just using var instead of val.
class BackgroundSyncWorker (
appContext: Context,
workerParams: WorkerParameters
): Worker(appContext, workerParams), KoinComponent {
val dataSyncRepository : DataSyncRepositoryImpl by inject()
override fun doWork(): Result {
return Result.success()
}
}
I have noticed a couple of things from your code:
The first reason for why this does not work because you need to extend/inherit the BackgroundSyncWork from KoinComponent, so making this BackgroundSyncWork koin-aware.
class BackgroundSyncWorker (
appContext: Context,
workerParams: WorkerParameters
): Worker(appContext, workerParams), KoinComponent {
val database: Database by inject()
override fun doWork(): Result {
return Result.success()
}
}
Second: Also, please make sure that database object creation is properly configured in koin module. It should work with no problem.