How to pass item ID to ViewModel? - android

I have little problem with pass Recyclerview item ID from Activity to ViewModel. I need this ID to edit objects.
Does anyone know how to do it in accordance with the MVVM architecture?

let's try this code, you can pass context object in constructor of ViewModel class and you can also pass binders object.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
myAddressActivityBinding= DataBindingUtil.setContentView(this#MyAddressActivity, R.layout.my_address_activity)
mMyAddressViewModel=MyAddressViewModel(this#MyAddressActivity)
myAddressActivityBinding!!.viewModel=mMyAddressViewModel
}
}
here you can find variable or id something like this, this may be your ViewMoidel class in which you are getting context object.
class MyAddressViewModel(val mMyAddressActivity: MyAddressActivity) : BaseObservable(), DeleteAdressCallback {
private val tilEmail = mMyAddressActivity.myAddressActivityBinding!!.tilEmail
}
and possible you have bound your object in XML too by using data

Related

Initialize value from parent constructor?

I have some parameter in the constructor. I need to convert it to something else and give it to the parent constructor. But the problem is that I want to remember the result of the conversion (which I give to the parent constructor) and I don't need to store it in the base class.
How initialize value in the parent constructor argument? Like "val" in base constructor?
protected open class BindingViewHolder(binding: ViewDataBinding) : RecyclerView.ViewHolder(binding.root)
protected open class ModelViewHolder<Model : Identifiable<*>?, Binding : ViewDataBinding>(
parent: ViewGroup,
inflateBinding: (LayoutInflater, ViewGroup, Boolean) -> Binding
) : BindingViewHolder(parent.inflateChildBinding(inflateBinding)) {
//some code
}
I have one problem in this code. I cant "val" "parent.inflateChildBinding(inflateBinding)"
In this case, you might as well make the binding a property in the first class. You also need to use the exact type of binding depending on the case, so you should apply generics like this:
protected open class BindingViewHolder<T: ViewDataBinding>(val binding: T) : RecyclerView.ViewHolder(binding.root)
At this point, your second class can be merged into the first class ad a secondary constructor so we can avoid overuse of inheritance:
protected open class BindingViewHolder<T: ViewDataBinding>(val binding: T) : RecyclerView.ViewHolder(binding.root) {
constructor(
parent: ViewGroup,
inflateBinding: (LayoutInflater, ViewGroup, Boolean) -> T
): this(parent.inflateChildBinding(inflateBinding))
}
If desired, you can make the primary constructor private. The property in it will still be public.
To answer your literal question, you would make a primary constructor with the parameter you want to keep as a property and then make a secondary constructor with the parameters you need. It would basically look like my example above but with your second class, and the primary constructor only passing the binding to the superconsttuctor.
You can store the result that you want to remember in a SharedPreference and retrieve it in the other class.
Storing data in SharedPreference
val sharedPref = context?.getPreferences(Context.MODE_PRIVATE) ?: return
with (sharedPref.edit()) {
putInt(getString(R.string.converted_value), convertedValue)
apply()
}
Retrieving data from SharedPreference
val sharedPref = context?.getPreferences(Context.MODE_PRIVATE)
I don't fully understand your question. If your goal is to get the value from the previous class, just pass them to the new class's constructor.
Read more on SharedPreference here

Cannot access ViewModel method from fragment

Probably it has a simple solution that I cant see. I have a fragment with a ViewModel, The Viewmodel has a method inside of it that I want to call from my fragment and supply the arguments for. but when I try to call the method it shows an error "Unsolved Reference"
class DetailFragmentViewModel : ViewModel() {
private val repo = Crepository.get()
private val itemIdlivedata = MutableLiveData<UUID>()
var crimeLiveDate: LiveData<Crime?> = Transformations.switchMap(itemIdlivedata){ it ->
repo.getitem(it) }
fun LoadItem(itemuuid:UUID){
itemIdlivedata.value = itemuuid
}
}
Fragment Class:
private val crimeDetailVM : ViewModel by lazy {
ViewModelProvider(this).get(DetailFragmentViewModel::class.java)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
crimeDetailVM.LoadItem <- Unsolved Reference
}
Thanks for the help!
EDIT:IT HAS A SIMPLE SOLUTION, I DID NOT CAST THE VIEW MODEL TO THE VIEW MODEL CLASS,THANKS FOR THE HELP EVERYONE
You are doing downcasting DetailFragmentViewModel to ViewModel. That is why you are not accessing to DetailFragmentViewModel methods.
Use
private val crimeDetailVM : DetailFragmentViewModel by lazy {
ViewModelProvider(this).get(DetailFragmentViewModel::class.java)
}
Instead of
private val crimeDetailVM : ViewModel by lazy {
ViewModelProvider(this).get(DetailFragmentViewModel::class.java)
}
Also this way is not idiomatic i suggest you to use kotlin extension
val viewModel by viewModels<DetailFragmentViewModel>()
But before do that you need to add the dependency which is Fragment KTX to your app gradle file.
https://developer.android.com/kotlin/ktx
You need activity context
try:
ViewModelProvider(requireActivity()).get(DetailFragmentViewModel::class.java)
you can use also extend view model by ActivityViewModel
eg.-> class DetailFragmentViewModel(application:Application) : AndroidViewModel(applivation){}

Best way to update values for views in activity from any class with Kotlin in android 4.1.1

I'm trying to update values in a TextView from an external class but not work, I'm trying with many ways y many posts but unlucky me... BTW sent the TextView like a parameter in class and works but we know that not is the best way if I have many views.
So first with a basic code:
In Main Activity XML:
<TextView
android:id="#+id/tvHello"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
In code:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
ClaseDePrueba(this#MainActivity)
}
}
Finally in class with my last proof
class ClaseDePrueba(ctx : Context)
{
val view = LayoutInflater.from(ctx).inflate(R.layout.activity_main,null,false)
view.tvHello.text = "New Value"
}
Even I try to use the Kotlin android extensions like this web site but not work for me
https://antonioleiva.com/kotlin-android-extensions/
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.activity_main.view.*
A few hours ago I tried to implement an interface but I don't know how to reference the TextView and chance value, my code:
class MainActivity : AppCompatActivity() , ClaseDePrueba.MyInterfaceClass {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
ClaseDePrueba(this#MainActivity)
}
override fun updateClass() {
TODO("Not yet implemented")
}
And my class
class ClaseDePrueba(ctx: Context)
{
interface MyInterfaceClass {
fun updateClass(
)
}
So the question is... What is the (best) way to fix and do it work correctly?
UPDATE and one solution
well I solved created a list of objects and pass as a parameter
I don't know if is the best way but at the moment works thanks all
class MainActivity : AppCompatActivity() , ClaseDePrueba.MyInterfaceClass {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val textViews = ArrayList<TextView>()
textViews.add(tvHello)
textViews.add(tvHello2)
ClaseDePrueba(this#MainActivity, textViews)
}
class ClaseDePrueba(ctx: Context, private val tviews: ArrayList<TextView>? = null)
{
init {
if (tviews != null) {
tviews[0].text = "Init1"
tviews[1].text = "Init2"
}
}
You can create an Interface and try to implement it in your activity.
interface MyInterface { fun update() }
Then try to access the function with the reference of class.
`Class MainActivity : AppCompatActivity() ,MyInterface{
override fun update(){
}
}'
Access this function with class reference and update the value.
If you have an Activity, and it inflates a layout, it creates the objects in the XML and assigns them IDs. The XML is like a recipe, the Activity is the cook, baking a delicious view layout.
So your Activity has some TextView objects. If you have another class, and that inflates the same XML file, it creates different objects. It's baking its own layout, and any changes you make to those View objects won't be seen in the Activity, because it has a completely different set of TextView instances (which happen to share ID values, because that's what the XML recipe says)
So if you want to mess with those TextViews in the Activity, you have three basic options:
have the Activity pass them to the other class, in a collection like a list. ("Here you are, here's the stuff you need to work with")
make them publicly accessible in the Activity, so you can pass the Activity instance to your other class, and it can poke them directly e.g. myActivity.coolTextView1.text = "wow!"
make some kind of interface on the Activity, e.g. fun updateText(id: Int, text: String) which the other class can call when it needs to update something. The Activity handles the details internally, like finding the relevant TextView object and updating it
either way, if you want to change those specific TextView instances displayed in your Activity, you need to access those instances somehow
Try doing,
class ClaseDePrueba(ctx : Context)
{
val txtView : TextView = (ctx as MainActivity).findViewById(R.id.tvHello) as TextView
txtView.setText("Hello")
}
(No need to inflate layout again)
The other way could be to implement an Interface

Android ViewModelProvider() parameter error

I am trying to get a value from the SharedViewModel class but the ViewModelProvider() is giving a parameter error when i am passing requireActivity() although the same initilization and assignment works in my fragments.
It is requiring "ViewModelStoreOwner" to be passed.
class CourseRepository(val app: Application) {
private var viewModel: SharedViewModel = ViewModelProvider(requireActivity()).get(SharedViewModel::class.java)
val courseData = MutableLiveData<List<Course>>()
init {
CoroutineScope(Dispatchers.IO).launch {
callWebService()
}
}
#WorkerThread
suspend fun callWebService() {
if (Utility.networkAvailable(app)) {
val retrofit = Retrofit.Builder().baseUrl(WEB_SERVICE_URL).addConverterFactory(MoshiConverterFactory.create()).build()
val service = retrofit.create(CourseService::class.java)
val serviceData = service.getCourseData(viewModel.pathName).body() ?: emptyList()
courseData.postValue(serviceData)
}
}
}
The purpose of the ViewModel here is because i am storing the Id of the selected RecyclerView item in order to send it to a server
ViewModel instances are scoped to Fragments or Activities (or anything with a similar lifecycle), which is why you need to pass in a ViewModelStoreOwner to the provider to get a ViewModel from it. The point of ViewModels is that they will exist until the store they belong to is destroyed.
The requireActivity method doesn't work here, because you're not inside a Fragment.
Some things to consider here:
Do you really need ViewModel in this use case? Could you perhaps use just a regular class that you can create by calling its constructor?
Could you call this Repository from your ViewModel, and pass in any parameters you need from there?

How can I initialize an androidx ViewModel from parcelable data?

In my Android app, I pass custom data (UByteArray) from one activity to another using the parcelable interface.
I am using this data inside multiple fragments, so I rewrote the data class to extend androidx ViewModel and expose LiveData properties to the fragments. Now the UI updates are a lot nicer, but I think I am using it wrong because I overwrite all ViewModel values inside onCreate.
Now my question: What do I need to change to initialize the ViewModel only once?
The following is my current code (abbreviated and renamed for this question):
class ActivityB : AppCompatActivity() {
private val bData: ViewModelB by viewModels()
// ...
override fun onCreate(savedInstanceState: Bundle?) {
// ...
intent.getParcelableExtra<ViewModelB>("id")?.let {
Log.e(TAG, "Found parceled bData $it")
// This seems like a very stupid way to do it, is there a better one?
bData.copyAll(it)
}
}
}
I saw that it is possible to inject SavedState into the ViewModelB constructor, but I don't have a saved state until now, and the data needs to be passed only once.
Should I change the initialization of tagData with by viewModels() to = ViewModelB(intent)?
Or do I need to extend the ViewModelFactory somehow?
Any tip here would be really appreciated, thanks.
I saw that it is possible to inject SavedState into the ViewModelB constructor, but I don't have a saved state until now, and the data needs to be passed only once.
The official solution would be to provide a SavedStateHandle that is initialized with the defaultArgs as the intent.extras of your Activity.
For that, you need to provide an AbstractSavedStateViewModelFactory implementation, OR use SavedStateViewModelFactory (in which case you must define the right constructor in order to have it instantiated via reflection).
class ActivityB : AppCompatActivity() {
private val bData: ViewModelB by viewModels {
SavedStateViewModelFactory(application, this, intent.extras)
}
// ...
override fun onCreate(savedInstanceState: Bundle?) {
// ...
// intent.getParcelableExtra<ViewModelB>("id")?.let {
// Log.e(TAG, "Found parceled bData $it")
}
}
Then in your ViewModel
#Keep
class ViewModelB(val savedStateHandle: SavedStateHandle): ViewModel() {
val uByteData = savedStateHandle.get<UByteArray>("id")
}
Or so. The "id" key must match the same key as is in the intent extras.
Since you have a ViewModel which implements Parcelable, you can get your ViewModelB instance directly from the Intent extra.
The Intent which is used for starting ActivityB may not be != null at the time when ActivityB is instantiated, but you can use
lateinit var bData: ViewModelB
Then in onCreate()
bData = if(intent.hasExtra("id")) intent.getParcelableExtra<ViewModelB>("id") else ViewModelProvider(this).get(ViewModelB::class.java)

Categories

Resources