For example java code
public abstract class BindingElement<T extends ViewDataBinding> {
T binding;
abstract public T createBinding(LayoutInflater inflater, ViewGroup parent);
public BindingElement(ViewGroup parent) {
binding = createBinding(LayoutInflater.from(parent.getContext()), parent);
binding.setLifecycleOwner(ViewTreeLifecycleOwner.get(parent));
}
}
I need some necessary property that defined in constructor. And then i will do something with that property. What is the best way write it in kotlin?
This doesn’t directly answer your question but provides a safer alternative.
You should avoid calling an open or abstract function from the constructor in Java or Kotlin, even though it’s allowed. It is fragile and can cause weird bugs that are difficult to resolve. Read here: In Java, is there a legitimate reason to call a non-final method from a class constructor?
An alternative in this case would be to make your function into a constructor parameter. Your class doesn’t even need to be open or abstract to support this.
class ViewBindingParameter<T: ViewBindingData> (
parent: ViewGroup,
inflateBinding: (LayoutInflater, ViewGroup)->T
) {
val binding: T = inflateBinding(LayoutInflater.from(parent.context), parent)
}
Usage:
val bindingParam = ViewBindingParameter(parent, SomeBinding::inflate)
If you aren't planning to add features to this class, you might as well just use a function that directly returns a binding so you don't have to deal with the wrapper class. Maybe an extension function of the parent view:
fun <T: ViewBindingData> ViewGroup.inflateChildBinding(inflateBinding: (LayoutInflater, ViewGroup)->T): T =
inflateBinding(LayoutInflater.from(context), this)
and use it like:
val binding = parent.inflateChildBinding(SomeBinding::inflate)
Kotlin is no different from Java in case of abstractions, so I assume something like below will work
abstract class BindingElement<T: ViewDataBinding> {
val binding: T
abstract fun createBinding(LayoutInflater inflater, ViewGroup parent): T
init {
binding = createBinding(...)
}
}
UPD: I noticed that your method requires field provided in constructor, so instead of init block you will use
constructor(parent: ViewGroup) {
binding = createBinding(...)
}
It may look more like Kotlin code
abstract class BindingElement<T: ViewDataBinding>(
val parent: ViewGroup
) {
val binding = createBinding(..., parent)
abstract fun createBinding(LayoutInflater inflater, ViewGroup parent): T
}
And this code is calling the non-final function in the constructor which is an unsafe operation.
Related
This Android tutorial introduces the concept of view binding, with this section demonstrating how to use it. In this case, the view binding is set up using the following code.
class MainActivity : AppCompatActivity() {
lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
}
}
The explanation for the call to ActivityMainBinding.inflate() is as follows:
This line initializes the binding object which you'll use to access
Views in the activity_main.xml layout.
What this does not explain is where the variable layoutInflater is defined.
When using Android Studio, the code completion suggests that the variable "comes from getLayoutInflater()":
getLayoutInflater() seems to be a method in Activity, but this doesn't help me understand what the reference to layoutInflater is doing, where it is defined, and how it is in scope at this point of the code. Can someone help me to understand this please?
ActivityMainBinding.java is the generated class by data binding which has a static method inflate(). When you pass the layoutInflater(it retrieve a standard LayoutInflater instance that is already hooked up to the current context) to inflate() it generates the same code under the code as we usually do while inflating the views and it fetches the layout name automatically.
So, the whole method is like
public static ActivityMainBinding inflate(#NonNull LayoutInflater inflater,
#Nullable ViewGroup parent, boolean attachToParent) {
View root = inflater.inflate(R.layout.activity_main, parent, false);
if (attachToParent) {
parent.addView(root);
}
return bind(root);
}
I hope this is what you are looking and sure can help you. Thanks
I came at the same question and found layoutInflater was declared in NavigationMenuPresenter.java.
package com.google.android.material.internal;
...
public class NavigationMenuPresenter implements MenuPresenter {
...
LayoutInflater layoutInflater;
...
public void initForMenu(#NonNull Context context, #NonNull MenuBuilder menu) {
layoutInflater = LayoutInflater.from(context);
...
In my case, full path to the java script was
%HOMEDRIVE%%HOMEPATH%\.gradle\caches\modules-2\files-2.1\com.google.android.material\material\1.7.0\289bbb3a7fea52532f1163487f9469217ee608a9\material-1.7.0-sources.jar!\com\google\android\material\internal\NavigationMenuPresenter.java
After a little digging I found out that the getLayoutInflater comes from the Activity class. Activity class is parent of androidx.core.app.ComponentActivity that is parent of ComponentActivity that is parent of FragmentActivity that is parent of AppCompatActivity that is parent of your Activity class.
This is probably very basic, but I can't seem to find a way to initialize a superclass with generated values.
For example, I want to inflate a view and then pass it to the superclass RecyclerView.ViewHolder.
In Swift I would do something like this:
class CustomViewHolder: RecyclerView.ViewHolder {
init(json: JSON) {
let view = getView(json)
super.init(view)
}
}
Is there a similar way in Kotlin? Or do I have to get the View outside and then pass it to both the CustomViewHolder and the RecyclerView.ViewHolder?
You can i.e. call a function from companion object:
class CustomViewHolder(json: JSON) : RecyclerView.ViewHolder(getView(json)) {
companion object {
private fun getView(json: JSON): View {
//...
}
}
}
I have been spending a lot of time trying to figure out why in the code below (towards the end), I get an error on ViewModelProvider(this). I also tried getActivity() instead of 'this', same issue. The error I get is "Cannot resolve constructor ..."
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
public class ItemSetupFragment extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_setup, container, false);
return view;
}
#Override
public void onViewCreated(#NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
ItemSetupFragmentModel model = new ViewModelProvider(this).get(ItemSetupFragmentModel.class);
model.getKids().observe(this, users -> {
// update UI
});
}
}
Firstly you need to use the latest version of lifecycle extension. It should be:
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation "androidx.lifecycle:lifecycle-viewmodel:2.2.0"
or any updated version.
Then you should use requireActivity() instead of getActivity(). This way you will ensure that the activity is attached an not getting a NullPointerException.
ItemSetupFragmentModel model = new ViewModelProvider(requireActivity()).get(ItemSetupFragmentModel.class);
Note: ViewModel Overview and Declaring Dependencies
I had to restart cache after adding the library to the Gradle file.
There is no need to use requireActivity(), this is enough.
You aren't using the latest library release in which the ViewModelProvider(#NonNull ViewModelStoreOwner owner) constructor was included. You are seeing the latest docs but not using the latest library version of ViewModel.
You need to use
implementation 'androidx.lifecycle:lifecycle-viewmodel:2.2.0' // For Java
or
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0' // For kotlin extension
The cleanest implementation must use Kotlin for its advanced functions. You can either create this kotlin code in a separate Kclass or leave this answer for future users that want to know how to do this in Kotlin. Basically we are initialising the ViewModel by lazy like this:
Make sure you have this dependency:
implementation "androidx.fragment:fragment-ktx:1.2.0"
Create this helper function that accesses an internal fragment-ktx method that allows yo to create a ViewModel instance by lazy:
#MainThread
inline fun <reified VM : ViewModel> Fragment.fragmentViewModel() =
createViewModelLazy(
VM::class,
{ this.viewModelStore },
{ ViewModelFactory(Database.getDatabase(requireContext().applicationContext)) }
)
Now create a ViewModelFactory using this official java example:
https://github.com/android/architecture-components-samples/blob/master/BasicRxJavaSample/app/src/main/java/com/example/android/observability/ui/ViewModelFactory.java
Or, here is the Kotlin variant:
class ViewModelFactory(private val database: Database?) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
requireNotNull(database) { "Database must not be null" }
return when {
modelClass.isAssignableFrom(ItemSetupFragmentModel::class.java) -> {
ItemSetupFragmentModel() as T
}
else -> {
throw IllegalArgumentException("Unknown ViewModel class")
}
}
}
}
And now go inside your fragment and simply initialise your ViewModel like this
class ItemSetupFragment : Fragment() {
private val model by viewModel<ItemSetupFragmentModel>()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
model.getKids().observe(this, users -> {
// update UI
});
}
}
Hope this helps!
You should instantiate your viewModel by :
ItemSetupFragmentModel model = ViewModelProviders.of(this).get(ItemSetupFragmentModel.class);
Expected Result
What am I'm trying to do is to inject my AccountType class to ExpandableAdpter and on click of child view what to check user Type?
How to implement dagger in Adapter?
Dagger working fine with Fragment and Activity. only getting null in adapter because unable to initialize adapter to dagger
Sample for interface in dagger
//Di interface
interface ActivityComponent : BaseComponent {
// adapter
fun inject(expDragSwipeAdapter: ExpandableDraggableSwipeableAdapter)
}
Adapter onCreate of group and childview
#Inject lateinit var accountType: Accounts
private lateinit var activityComponent: ActivityComponent
override fun onCreateGroupViewHolder(parent: ViewGroup, viewType: Int): MyGroupViewHolder {
activityComponent.inject(this)
val inflater = LayoutInflater.from(parent.context)
val v: View
if (isDragRequire) {
v = inflater.inflate(R.layout.row_edit_watchlist, parent, false)
} else {
v = inflater.inflate(R.layout.row_watchlist, parent, false)
}
return MyGroupViewHolder(v, isDragRequire, mContext)
}
override fun onCreateChildViewHolder(parent: ViewGroup, viewType: Int): MyChildViewHolder {
activityComponent.inject(this)
val inflater = LayoutInflater.from(parent.context)
val v = inflater.inflate(R.layout.row_child_watchlist, parent, false)
return MyChildViewHolder(v, false)
}
I'm facing the error in this line activityComponent.inject(this)
On The onclick checking AccountType and implement business logic
There is no need to request injection from a Dagger 2 Component inside an Adapter for a RecyclerView or a ListView.
For Fragments and Activities we have no choice other than to explictly request injection from a Component since these objects are instantiated by the Android OS and we don't "control" the constructors.
For everything else, including Adapters, you should prefer constructor injection and then setting parameters manually.
Something idiomatic would look something like the following. Inside your Fragment:
class MyFragment : Fragment {
#Inject
lateinit var accountsAdapter: accountsAdapter
#Inject
lateinit var accountsRepository: AccountsRepository
//load accounts in onStart or wherever you decide to load
//when loading finished, execute the following method in a callback
fun onAccountsLoaded(accounts: Accounts) {
adapter.setAccounts(accounts)
}
}
For example, your Adapter could do something like:
class Adapter #Inject constructor() : BaseAdapter {
fun setAccounts(accounts: Accounts) {
this.accounts = accounts
notifyDataSetChanged()
}
}
You can see the official Google Android Architectural examples for using a ListView with Dagger 2. The link is here
You can take a look at Assisted Injection in Dagger which allows you to inject types at runtime. https://dagger.dev/dev-guide/assisted-injection
I know that generally it shouldn't make a difference that this is using Kotlin, but I've run into odd cases where the #Named qualifier needed a scope in Kotlin.
I have a ViewHolderFactory class that allows me to create a simple mapping of view type -> view holder class:
#Singleton
class ViewHolderFactoryImpl #Inject constructor(
private val viewHolderComponentProvider: Provider<ViewHolderSubcomponent.Builder>
): ViewHolderFactory(mapOf(
R.layout.view_error to ErrorViewHolder::class.java,
R.layout.view_soft_error to SoftErrorViewHolder::class.java,
R.layout.view_empty to EmptyViewHolder::class.java,
R.layout.view_loading to LoadingViewHolder::class.java,
R.layout.item_got_it to GotItViewHolder::class.java)) {
override fun createViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val viewHolder = super.createViewHolder(parent, viewType)
if (viewHolder is Injectable) {
viewHolderComponentProvider.get()
.viewHolder(viewHolder)
.build()
.inject(viewHolder)
}
return viewHolder
}
}
The ViewHolderSubcomponent is defined below, the goal is to be able to create one subcomponent for each view holder and inject a few things:
#ViewHolderScope
#Subcomponent(modules = [ViewHolderModule::class])
interface ViewHolderSubcomponent {
fun inject(viewHolder: RecyclerView.ViewHolder)
fun viewHolder(): RecyclerView.ViewHolder
#Subcomponent.Builder
interface Builder {
#BindsInstance
fun viewHolder(viewHolder: RecyclerView.ViewHolder): Builder
fun build(): ViewHolderSubcomponent
}
}
The ViewHolderModule is defined as:
#Module
class ViewHolderModule {
#Provides #ViewHolderScope
fun provideSectionTitleViewHolder(viewHolder: RecyclerView.ViewHolder): SectionTitleViewHolder =
SectionTitleViewHolder(viewHolder.itemView)
}
When I run the app I find that the injection didn't work, my #Inject lateinit var values are null. Looking at the generated code I can see why:
#Override
public void inject(RecyclerView.ViewHolder viewHolder) {
MembersInjectors.<RecyclerView.ViewHolder>noOp().injectMembers(viewHolder);
}
There's no MembersInjectors<RecyclerView.ViewHolder> created for this subcomponent. I can't figure out how to get this to work. I know that I should be able to inject into objects not created by dagger, I just can't quite figure out what I'm missing here.
Oh, if it helps any, I did make sure to include the ViewHolderSubcomponent in my AppModule's list of subcomponents
inject(viewHolder: RecyclerView.ViewHolder) will always be a no-op, because framework classes (or most libraries in this case) don't have any #Inject annotated fields. Dagger will only generate code for the class named in your inject(MyClass instance) methods, not for any of its sub-types.
So if you have a ErrorViewHolder : RecyclerView.ViewHolder, then you have to use a component that has a inject(ErrorViewHolder instance) method to generate code to inject ErrorViewHolder.
To clarify, because it's generated code—and not dynamic reflection at runtime—calling inject(viewHolder: RecyclerView.ViewHolder) like you do with an viewHolder : ErrorViewHolder will still try to inject fields for RecyclerView.ViewHolder, not ErrorViewHolder. And RecyclerView.ViewHolder will always be a no-op as mentioned.
You will have to modify your setup quite a bit so that you can provide a specific subcomponent that can inject a specific viewholder, you can't use one "generic" component for different types. You could create a base class between RecyclerView.ViewHolder and ErrorViewHolder, but then again, you could only inject fields declared (and #Inject annotated) in your base class, not the specific child.
The Kotlin proptery itself is not known to Dagger. What you are trying to achieve could be handled as a field or setter injection. Since a setter is generated with any property I usually go for #set:Inject.
#set:Inject lateinit var myVar: Type
Alternatively you can consider constructor injections. Like this you can define the properties as val and private.