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
Related
I am working on a custom dialog fragment, that is being used/ called from two different views having different viewModels. Instead of passing two separate viewModels in the constructor parameter of Dialog class as,
class CustomeDialog(var viewModel1: ViewModelA ?= null, var viewModel2 : ViewModelB ?= null) : DialogFragment()
I need to ask/ figure out a way where I could just set < T> kind of parameter to dialog so I could just type caste any viewModel to it, I want.
something like this,
class CustomDialog<T:ViewModel> : DialogFragment()
and in code, it would be something like
val mdialog1: CustomeDialog by lazy { CustomeDialog(viewModel as ViewModelA) }
and also
val mdialog2: CustomeDialog by lazy { CustomeDialog(viewMode2 as ViewModelB) }
You can create a secondary constructor in the generic class that takes in a generic ViewModel parameter:
class CustomeDialog<T : ViewModel>() : DialogFragment() {
constructor(viewmodel: T) : this()
}
And the usage the same as you did:
lateinit var viewModel: ViewModel
val mdialog1: CustomeDialog<ViewModelA> by lazy { CustomeDialog(viewModel as ViewModelA) }
lateinit var viewModel2: ViewModelA
val mdialog2: CustomeDialog<ViewModelA> by lazy { CustomeDialog(viewModel2) }
UPDATE:
how to initialize viewModel in dialog based on the type. eg. if VM1 is passed in constructor, then var dialogViewModel = WHAT??,
It's requested to have a Dialog with a generic ViewModel, so its type is Generic as it's unknown till it's instantiated.
yeah i need a local var dialogViewModel which is generic, as i mentioned, whole logic is dependent on this dvm
You can initialize it in the secondary constructor:
class CustomDialog<T : ViewModel>() : DialogFragment() {
lateinit var dialogViewModel: T
constructor(viewmodel: T) : this() {
dialogViewModel = viewmodel
}
}
This strategy cannot work. The OS recreates your Fragment using reflection and its empty constructor. It can restore state to the replacement Fragment using Bundle values, but class types are not a valid type of data for a Bundle.
Closest I can come up with is to make it an abstract class, and then create simple subclasses that have concrete types.
abstract class CustomDialog<T: ViewModel>(viewModelType: KClass<out T>): DialogFragment() {
val viewModel: T by createViewModelLazy(viewModelType, { viewModelStore })
}
class CustomDialogA: CustomDialog<ViewModelA>(ViewModelA::class)
class CustomDialogB: CustomDialog<ViewModelB>(ViewModelB::class)
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)
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
I have a few cases where I want to add static functions or values in a base class so that I can use them in all subclasses that inherits from it.
One such case is when i want to create generic tags for each class to use in data mapping as a key, like when i want to find fragments or pass data between activities.
For example:
open class BaseClass(){
companionObject{
val TAG: String = this.javaClass.simpleName
}
}
class ChildClass: BaseClass()
class Main: Activity(){
fun startActivity(){
val intent = Intent(this, ChildClass::class.java)
intent.putExtra(ChildClass.TAG, data)
startActivity(intent)
finish()
}
}
Can this be done or am I forced to create an companion object for each class?
I don't know a solution with companions. But you could use a global reified inline function for the specific use case, you mentioned in your question:
open class BaseClass()
class ChildClass: BaseClass()
inline fun <reified T> tagOf() = T::class.java.simpleName
fun main(args: Array<String>) {
println(tagOf<BaseClass>())
println(tagOf<ChildClass>())
}
Hm... I think, you can't do it. As mentioned in this article: https://proandroiddev.com/a-true-companion-exploring-kotlins-companion-objects-dbd864c0f7f5
companion object is really a public static final class in your BaseClass. So, I think, you can't do this.
I am working on an android project. I want to make an abstract subclass of FrameLayout with an abstract method
#LayoutRes
abstract fun getLayoutToInflate(): Int
In the constructor I want to inflate the layout returned by this method. But the IDE shows a warning about "Calling non-final function in constructor..." at this code
val inflater = LayoutInflater.from(context)
inflatedBanner = inflater.inflate(getLayoutToInflate(), this, true)
This app doesn't build yet. So wrote a simple kotlin code like this to test.
abstract class Base {
val text: String
constructor(text: String) {
this.text = text
println(text + getTextSuffix())
}
abstract fun getTextSuffix(): String
}
class Derived(text: String) : Base(text) {
override fun getTextSuffix() = "_"
}
fun main(args: Array<String>) {
val d = Derived("stuff")
}
This code always prints "stuff_" which means that the overridden abstract method is available in constructor.
Can I rely on this behaviour in my app too? If not, what is the correct way to implement something like this in kotlin?
Kotlin here is no different from Java or most other OOP languages.
As long as you make it clear in the method's contract that the overriding
methods must not access any state in the subclass, you can safely call them from the base class's constructor. If a class breaks this rule, its method will be accessing uninitialized state.