How can i handle two way binding in MVI architecture? - android

I see many articles handling two way binding with MVVM. But i want to handle this issue with single source of truth. I dont want to create multiple stateflows for this case. I have a StateFlow which has all view state inside entity. How can i provide to sync with multiple EditText values to the StateFlow ?
I have added multiple TextWatcher's to sending event to ViewModel but this way causes so boilerplate code.

I got your question and I think that you need to use viewBinding feature,
so the first step you need to enable viewBinding features in your gradle (module).
android{
buildFeatures {
viewBinding = true //this is mandatory
}
...
}
then you can bind your view
I use my code as example
inside the activity class
you can declare viewBinding of your activity using lateinit and inflate your activity inside onCreate fun
class HomeActivity : AppCompatActivity() {
private lateinit var binding: ActivityHomeBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityHomeBinding.inflate(layoutInflater)
setContentView(binding.root)
...
}
}
and finally you can easily access your view using binding val
example
binding.textView.text = "example"
the code above will be similar to
findViewById<TextView>(R.id.text_view).text ="example"

Related

Why do I use pure components but still need fragment dependencies?

I have a problem when using compose, then i found the answer
If you use Compose with Fragments, then you may not have the Fragments dependency where viewModels() is defined.
Adding:
implementation "androidx.fragment:fragment-ktx:1.5.2"
use Compose with Fragments, but I use Pure Compose, Also had this problem.
What am I missing? Or is there some connection between fragment and compose?
#AndroidEntryPoint
class MainActivity : ComponentActivity() {
private val userViewModel: UserViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Content(userViewModel)
}
}
}
#Composable
fun Content(userViewModel: UserViewModel) {
val lazyArticleItem = userViewModel.list().collectAsLazyPagingItems()
thread {
repeat(200) {
userViewModel.insert(User())
}
}
LazyColumn(verticalArrangement = Arrangement.spacedBy(16.dp)) {
items(lazyArticleItem) { user ->
Text("user ${user?.id}")
}
}
}
The above is my ui interface code, based on this, I don't think I'm using fragment.
I want to declare my logic. I use Pure Compose instead of Fragment, but actually want to run the code must depend on androidx.fragment:fragment-ktx:1.5.2
It happens because you are using
val userViewModel: UserViewModel by viewModels()
You can access a ViewModel from any composable by calling the viewModel() function.
Use:
val userViewMode : UserViewModel = viewModel()
To use the viewModel() functions, add the androidx.lifecycle:lifecycle-viewmodel-compose:x.x.x
In programming, "fragment" and "compose" can refer to two related concepts:
Fragment: In UI design, a fragment is a portion of an activity's UI, which can be reused in multiple activities or combined to form a single activity. It provides a way to modularize the UI and make it more manageable.
Compose: Compose is a modern UI toolkit for Android app development introduced by Google, which allows developers to build and style UI elements using composable functions. It provides a way to create and reuse UI components that can be combined to form a complete app UI.
Both concepts are aimed at making UI design and development more modular, reusable and maintainable.
Hope this helps!

Kotlin how to make a ViewBinding by viewBindings() similar to ViewModel by viewModels()

In Kotlin there is this nice extension function that allows us to write
var viewModel: MyViewModel by viewModels()
Is it possible to have the same thing for ViewBindings?
Currently I am using this (in Activities):
var viewBinding: MyViewBinding by lazy { MyViewBinding.inflate(layoutInflater) }
It works but feels a bit clumsy. I'd rather have it like the following but do not know how to implement that:
var viewBinding: MyViewBinding by viewBindings()
For sure you can write a property delegate for a viewbinding for any class that holds views. You can check out this library for inspiration, or even use it in your project

Unresolved reference - activity does not recognize synthetic imports in android studio v4

last night I noticed I'm not able to change the attributes of elements in the layout from my main activity
so I built a new project and I had the same problem there too.
I could not find out what was wrong with my android studio so I'd appreciate it if someone with the same problem helps me out.
as you see in the picture when I call a defined view from the layout in my activity its not recognized
the error will be: Unresolved reference: txtHello
Kotlin Synthetic imports not working ?
Well, there's always the age-old alternative:
val foo: TextView = findViewById(R.id.your_id)
I believe synthetics have been deprecated and I guess support for it has just now been completely removed
Alternatively, you can make use of ViewBinding, which is another alternative.
Enable it in your build.gradle:
android {
...
buildFeatures {
viewBinding true
}
}
This generates a Binding object for your layout, so you can make use of it like this:
private lateinit var binding: YourLayoutNameBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = YourLayoutNameBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
}
then you have access to all views on the layout, through the binding:
binding.name.text = "foo"
An alternative you can look at is ViewBinding, a concept in Android that was introduced recently.
You should take a look for this
https://developer.android.com/topic/libraries/view-binding
You cannot set view id directly for your use in app, instead you need findViewById(R.id.idTextHello).setOnClickListener()
That's how views are bind in application.
import kotlinx.android.synthetic.main.activity_main.*
but dont forget to apply kotlin extension
You are trying to access your views via Kotlin Synthetics, which have been deprecated.
You can use ViewBinding instead.
Enable it in your module level build.gradle:
android {
...
buildFeatures {
viewBinding true
}
}
And then in your activity you can access views like :
private lateinit var _binding: ActivityNameBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
_binding = ActivityNameBinding(layoutInflater)
val view = binding.root
setContentView(view)
}
and then you can access your views like this:
_binding.btn_start.setOnClickListener {
...
}
For detailed understanding of ViewBinding, you can look into this article:
https://medium.com/geekculture/android-viewbinding-over-findviewbyid-389401b41706

IllegalArgumentException:SavedStateProvider with the given key is already registered

My app is crashing on some user's devices with the exception below.
Fatal Exception: java.lang.IllegalArgumentException: SavedStateProvider with the given key is already registered
at androidx.savedstate.SavedStateRegistry.registerSavedStateProvider(SavedStateRegistry.java:2)
at androidx.lifecycle.SavedStateHandleController.attachToLifecycle(SavedStateHandleController.java:2)
at androidx.lifecycle.SavedStateHandleController.create(SavedStateHandleController.java:1)
at androidx.lifecycle.AbstractSavedStateViewModelFactory.create(AbstractSavedStateViewModelFactory.java:1)
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:5)
at androidx.lifecycle.ViewModelLazy.getValue(ViewModelLazy.java:5)
at androidx.lifecycle.ViewModelLazy.getValue(ViewModelLazy.java:5)
at com.emptysheet.pdfreader_autoscroll.homeScreen.MainActivity.getViewModel(MainActivity.java:3)
at com.emptysheet.pdfreader_autoscroll.homeScreen.MainActivity$scanDeviceForFiles$1$1.invokeSuspend(MainActivity.java:3)
at kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull(Intrinsics.java)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.java:4)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.java)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.java:7)
Everything is working fine on my devices. I don't get this exception on my own testing devices as well as on emulators. Also, I am using Hilt in my app.
Here is my ViewModel class.
class MainActivityViewModel #ViewModelInject constructor(
private val pdfItemRepository: PdfItemRepository
) : ViewModel() {
}
Here is the activity where I use this ViewModel.
#AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private val viewModel:MainActivityViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
}
I am not using this MainActivityViewModel anywhere else except MainActivity. So there is no sharing of ViewModel. Also, the rotation is off on MainActivity. So there is no rotation change.
I came to understand from the error below that somehow my activity is trying to create another ViewModel instead of retaining the previous one?. Please correct me if I am wrong. I am unable to understand what kind of scenarios is triggering SavedStateRegistry.registerSavedStateProvider() again.
Note - I have omitted methods in my ViewModel class and activity to increase readability.
I faced the same exception
and managed to fix that by going to app/build.gradle file and:
changing the configuration name from 'annotationProcessor' to 'kapt'
for these artifacts:
'com.google.dagger:dagger-compiler:2.28.3',
'androidx.hilt:hilt-compiler:1.0.0-alpha01'
and sync the project.
You can find a useful explanation on the scenario behind the seen in the link below:
https://zsmb.co/a-deep-dive-into-extensible-state-saving/
You must to add this dependencies to your
implementation 'androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha02'
// When using Kotlin
kapt 'androidx.hilt:hilt-compiler:1.0.0-alpha02'

Kotlin Android - Is there a way to define the Views only one time in a class?

In my code I make use of the following Views in XML:
val googleButton: Button = findViewById<View>(R.id.google_login) as Button
val loginWithEmailText: TextView = findViewById(R.id.login_with_email_text)
val emailLoginButton: Button = findViewById(R.id.email_login_button)
val createAccountButton: Button = findViewById(R.id.email_create_account_button)
This code is extracted from a function inside my Kotlin class. Whenever I have to access these views, I need to write this code all over again.
Is there any way that I can access them from only one place in my class code? I tried putting them outside but the app won't start.
Thank you
You need to define these fields as a part of your class and initialize them once you set the layout resource for your Activity/Fragment. If you put these lines 1:1 in the class body, the initialization will fail, since the layout has not been inflated yet.
Please get familiar with the concept of lifecycle, so that you can understand how to approach View related topics: https://developer.android.com/guide/components/activities/activity-lifecycle
Please check out this snippet for a sample code:
class MyActivity: Activity() {
lateinit var textView: TextView
lateinit var button: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_my)
// initialize your views here
textView = findViewById(R.id.text_view_id)
button = findViewById(R.id.button_id)
}
fun someOtherFunction(){
// you can reference your views here like normal properties
button.setOnClickListener { v -> callAnotherFunction() }
// ...
}
}
Since you are on Android, you might be interested in using Kotlin synthetic properties for referencing views without the whole boilerplate of finding them: https://antonioleiva.com/kotlin-android-extensions/. It's no longer a recommended practice to make use of it, but it's handy in some cases anyway.

Categories

Resources