I'm trying to build an app with the following architecture: LoginActivity -> MainActivity -> everything else handled in fragments hosted by MainActivity. I'm also using the Kodein Framework for the first time and get the following error in my starting fragment:
Property delegate must have a 'provideDelegate(HomeFragment,
KProperty*>' method. None of the following functions is suitable.
provideDelegate(Context [highlighted in red], KProperty<>?) defined
in org.kodein.di.android.KodeinPropertyDelegateProvider Type
'KodeinPropertyDelegateProvider' has no method
'getValue(HomeFragment, KProperty<>)' and thus it cannot serve as a
delegate
This is my code so far:
class HomeFragment : Fragment(), KodeinAware {
override val kodein by kodein()
private val factory : MainViewModelFactory by instance()
private lateinit var viewModel: MainViewModel
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val binding : FragmentHomeBinding = FragmentHomeBinding.inflate(inflater, container, false)
viewModel = ViewModelProviders.of(this, factory).get(MainViewModel::class.java)
binding.viewModel = viewModel
return binding.root
}
}
How can I fix this?
Thanks :)
Nevermind, adding a type declaration after kodein did the trick... :)
In your imports change
import org.kodein.di.android.kodein
to
import org.kodein.di.android.x.kodein
You can do it like this:
override val kodein:Kodein by kodein()
Related
Aim is to declare a base class
abstract class BaseDialog<T : ViewBinding> : AppCompatDialogFragment() {
lateinit var binding: T
}
and all child classes should extend this parent class
class ChildClass: BaseDialog<ChildClassViewBinding>() {
}
Then I want to inflate the binding in parent class and save it to binding property
This seems out of my scope of knowledge of kotlin
Is this really possible to do?
If I were to do this, I'd do it like this:
class BaseDialogFragment<T: ViewBinding>(private val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> T)
: AppCompatDialogFragment() {
var _binding: T? = null
val binding: T get() = _binding ?: error("Must only access binding while fragment is attached.")
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = bindingInflater(inflater, container, false)
return binding.root
}
override fun onDestroy() {
super.onDestroy()
_binding = null
}
}
with usage like:
class ChildClass: BaseDialog(ChildClassViewBinding::inflate) {
}
However, I would not do this in the first place (since there's a nice alternative). It can become messy pretty quickly to rely on inheritance for these kinds of things. What happens if you want to add some other features for a dependency injection framework, or some other common things you like to use? What if there's some features you like to use in some of your fragments but not all of them? And are you also creating base classes like this for Activity and non-Dialog Fragments?
These problems are why there's a programming axiom: "composition over inheritance".
Sometimes there's no choice but to use inheritance to avoid code duplication. But in the case of Fragments and Bindings, I don't think so. You can pass your layout reference to the super constructor, and use ViewBinding.bind() instead of inflate(). Since bindings rarely need to be accessed outside the onViewCreated function, you usually don't need a property for it.
class ChildClass: AppCompatDialogFragment(R.layout.child_class_view) {
override fun onViewCreated(view: View, bundle: Bundle?) {
super.onViewCreated(view, bundle)
val binding = ChildClassViewBinding.bind(view)
//...
}
}
If you do need a property for it, #EpicPandaForce has a library that makes it a one-liner and handles the leak-avoidance on destroy for you inside a property delegate.
Library here
Usage:
class ChildClass: AppCompatDialogFragment(R.layout.child_class_view) {
private val binding by viewBinding(ChildClassViewBinding::bind)
}
Create Base Fragment
abstract class BaseFragment<VB : ViewBinding> : Fragment() {
private var _bi: VB? = null
protected val bi: VB get() = _bi!!
abstract val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> VB
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
_bi = bindingInflater(inflater, container, false)
return _bi!!.root
}
override fun onDestroyView() {
super.onDestroyView()
_bi = null
}
}
In your Child Fragment
class HomeFragment : BaseFragment<HomeFragmentBinding>() {
override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> HomeFragmentBinding
get() = HomeFragmentBinding::inflate
}
I have an app with a single activity but with many Fragments. I am using ViewModel for my Activity-Fragment communication. Lately, I am using Hilt, and I am having a problem now communicating between my activity and fragments.
My Viewmodel
#HiltViewModel
class AppViewModel #Inject internal constructor(
): ViewModel() {
private var _data = MutableLiveData<String>()
val data: LiveData<String>
get() = _data
fun insertData(dataStr: String) {
_data.value = dataStr
}
}
My MainActivity
#AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private val mViewModel: AppViewModel by viewModels()
private var dataString: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mViewModel.data.observe(this, {
dataString = it
})
}
}
One of my Fragments
#AndroidEntryPoint
class ReportFragment : Fragment() {
private val reportViewModel: ReportViewModel by viewModels()
private val appViewModel: AppViewModel by viewModels()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View {
...
appViewModel.insertData("Hello")
...
}
}
When I run the app, I am getting null as a result of data. Any solution to solve this?
Not sure if this is the exact issue, but you get the ViewModel inside your fragment using by activityViewModels<AppViewModel> and not by viewModels
EDIT:
Also, I just noticed you are using an internal constructor. Try using only inject constructor once and let me know if it fixed it for you :)
I'm trying to implement a BaseFragment in which I will pass the layout resource on it and it should outputs the binding to work in the fragment itself instead of need to do it everytime the fragment is extended.
For example I have this BaseFragment
open class BaseFragment(#LayoutRes contentLayoutId : Int = 0) : Fragment(contentLayoutId) {
private lateinit var onInteractionListener: OnFragmentInteractionListener
val toolbar : Toolbar?
get() {
return if(activity is BaseActivity)
(activity as BaseActivity).toolbar
else
null
}
override fun onAttach(context: Context) {
super.onAttach(context)
setOnInteractionListener(context)
}
...
In which I use like this
class A(): BaseFragment(R.layout.myFragment) { ... }
Now, if I use this I will need to do the definition of the binding class again in my onCreateView
class A(): BaseFragment(R.layout.myFragment) {
private lateinit var binding: MyFragmentBinding
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
binding = DataBindingUtil.inflate(inflater, R.layout.myFragment, container, false)
return binding.root
}
override fun onDestroy(){
binding = null
}
}
What I want to implement is that since I'm passwing the layout to my BaseFragment, I want my BaseFragment to handle the creation of the binding and just return me the binding in the fragment which I use to extend BaseFragment
What I want to have is something like this
class A(): BaseFragment(R.layout.myFragment) {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.myTextView = ""
}
}
So my question is, how I can implement inside BaseFragment the onDestroy() and the onCreateView to always create a binding for me from the layout I'm passing in ?
I heard that I should use reflection but I'm not that sure on how to accomplish it
I didn't hear about the possibility to get the databinding just from a layout, but even if it's possible, I don't think that is the recommended way, because of two reasons:
Reflection is slow
It makes things more complicated than they are.
Instead of making magic with Reflection, you could do something like this:
abstract class BaseFragment<out VB: ViewDataBinding>(
private val layout: Int,
// Other Dependencies if wanted
) : Fragment() {
abstract val viewModel: ViewModel
// other variables that all fragments need
// This does not cause any memory leak, because you are not storing the binding property.
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? = DataBindingUtil.inflate<VB>(inflater, layout, container, false).apply {
lifecycleOwner = viewLifecycleOwner
setVariable(BR.viewModel, viewModel)
}.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
// Do some basic work here that all fragments need
// like a progressbar that all fragments share, or a button, toolbar etc.
}
And then, when you still need the bindingProperty, I would suggest the following library (it handles all the onDestoryView stuff etc):
implementation 'com.kirich1409.viewbindingpropertydelegate:viewbindingpropertydelegate:1.2.2'
You can then use this like:
class YourFragment(yourLayout: Int) : BaseFragment<YourBindingClass>() {
private val yourBinding: YourBindingClass by viewBinding()
override val viewModel: YourViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
// do binding stuff
}
}
Let me know if this worked for you.
Cheers
Am Using Koin as Dependency injection pattern in my project, I need to create new instances whenever i load fragment/activity, now am using the following pattern, Any solution for that it might save lots of time.
private val homeViewModel: HomeViewModel by viewModel()
The question is why you want new instances everytime? The whole concept of ViewModel is to retain the same instance and data. viewModel {} creates new instance everytime you inject it unless it is not shared.
Don't know why it is not working for you, but I think you can use factory{} instead of viewModel{}.
factory{
// this is because you need new instance everytime.
HomeViewModel()
}
Define ViewModel as an abstract in BaseFragment class and set value when you extend your BaseFragment.
abstract class BaseFragment<Binding : ViewDataBinding, ViewModel : BaseViewModel> : Fragment(){
protected var bindingObject: Binding? = null
protected abstract val mViewModel: ViewModel
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
bindingObject = DataBindingUtil.inflate(inflater, getLayoutResId(), container, false)
return bindingObject?.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
performDataBinding()
}
#LayoutRes
abstract fun getLayoutResId(): Int
private fun performDataBinding() {
bindingObject?.setLifecycleOwner(this)
bindingObject?.setVariable(BR.viewModel, mViewModel)
bindingObject?.executePendingBindings()
}
}
And in your fragment
class FragmentNew : BaseFragment<FragmentNewBinding, FragmentNewVM>() {
// Here is the your viewmodel imlementation. Thus when you create fragment it's by default override method
override val mViewModel: FragmentNewVM by viewModel()
override fun getLayoutResId(): Int = [fragment layout id like "R.layout.fragment_new"]
}
You are going to want to forego using by viewmodel and instantiate the class directly. You can get global (scoped) variables through getKoin().get().
private val viewModel = HomeViewModel(getKoin().get())
I am starting on a new project and I will be working in a team of 10 developers. I am setting up the base structure for our Android app. As I am working with a team and I want everyone to follow the same structure i.e creating ViewModel for each fragment and use data binding. How can I make it strict so that developers get an error if they don't create ViewModel for their Fragment?
So I have created the below BaseFragment:
abstract class BaseFragment<out VM : BaseViewModel, DB : ViewDataBinding> : Fragment() {
open lateinit var binding: DB
private fun init(inflater: LayoutInflater, container: ViewGroup?) {
binding = DataBindingUtil.inflate(inflater, getLayoutRes(), container, false)
}
#LayoutRes
abstract fun getLayoutRes(): Int
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View {
init(inflater, container)
super.onCreateView(inflater, container, savedInstanceState)
return binding.root
}
open fun refresh() {}
}
How can I improve it more?
One of the possible ways to improve your base fragment is to use reified like this:
protected inline fun <reified T : ViewModel> getViewModel(): T =
ViewModelProviders.of(this)[T::class.java]
and call is:
private val loginViewModel: LoginViewModel = getViewModel()
Useful links on this approach:http://www.albertgao.xyz/2018/05/22/3-ways-to-handle-view-model-creation-in-android-with-dagger-and-kotlin/
How does the reified keyword in Kotlin work?
https://proandroiddev.com/how-reified-type-makes-kotlin-so-much-better-7ae539ed0304