How to inject viewmodel in a customView with koin? - android

I know how to inject viewmodel in Activities or Fragments with koin:
private val regionSelectorViewModel: RegionSelectorViewModel by viewModel()
Right now I am setting viewmodel to my customView like this:
fun setViewModel(viewModel: RegionSelectorViewModel) {
mViewModel = viewModel
}
The viewmodel is initialized in Activity and passed through parameter to view. But... I would like to inject viewmodels in customViews as I do in the activities or fragments. Is there a way to do that using koin?

In the end I got a solution for this problem, we only have to get viewmodel from activity context:
private val viewModel: VersionViewModel by lazy {
(context as FragmentActivity).getViewModel()
}
Another solution, or for me the best solution is create a delegate to get viewmodel from a view.
inline fun <reified T : ViewModel> ViewGroup.viewModel(): ReadOnlyProperty<ViewGroup, T> =
object : ReadOnlyProperty<ViewGroup, T> {
private var viewModel: T? = null
override operator fun getValue(
thisRef: ViewGroup,
property: KProperty<*>
): T = viewModel ?: createViewModel(thisRef).also { viewModel = it }
private fun createViewModel(thisRef: ViewGroup): T {
return (thisRef.context as FragmentActivity).getViewModel()
}
}
class CustomView #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
defStyleRes: Int = 0
) : FrameLayout(context, attrs, defStyleAttr, defStyleRes) {
private val viewModel: CustomViewModel by viewModel()
}

Related

Jetpack Compose view not drawing when coming back to fragment

Using a Compose view that inherent from AbstractComposeView
inside an XML ui code of a fragment
Knowing that this fragment is part of a navigation graph (Jetpack navigation)
When i press the back button going back to my fragment, the compose view just disappeared.
It's only drawing for the first time i open the fragment.
Bellow view code
class ProgressComposeView #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : AbstractComposeView(context, attrs, defStyleAttr) {
private var steps = mutableStateOf(0)
private var currentStep: Int = 0
private var windowWidth: Int = 0
#Composable
override fun Content() {
ProgressView(steps.value, currentStep, windowWidth)
}
fun setData(steps: Int, currentStep: Int, windowWidth: Int) {
this.steps.value = steps
this.currentStep = currentStep
this.windowWidth = windowWidth
}
}
#Composable
fun ProgressView(totalSteps: Int, currentStep: Int, windowWidth: Int) {
..... }
Solution :
You have to call .disposeComposition() on your ComposeView
In your onResume() function
Example :
override fun onResume() {
super.onResume()
binding.composeHeader.disposeComposition()
}

how to create secondary constructor in recycler adapter taking arraylist parameter in kotlin android studio

enter image description here
I AM trying to create other constructor in recyclerView adapter but getting error
please tell me how to create secondary constructor in recycler adapter taking arraylist parameter in kotlin android studio
class CustomRecyclerAdapter constructor(
val context1: Context,
val topics: Array<String>,
private var alist: ArrayList<Array<String>>
) : RecyclerView.Adapter<CustomRecyclerAdapter.ViewHolder>() {
constructor(sss: ArrayList<Array<String>>, ttt: Array<String>) : this(sss) {
this.alist = sss
}
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): CustomRecyclerAdapter.ViewHolder {
val layout = LayoutInflater.from(parent.context).inflate(R.layout.cardlayout, parent, false)
return ViewHolder(layout)
}
override fun onBindViewHolder(holder: CustomRecyclerAdapter.ViewHolder, position: Int) {
holder.topicTextView.text = topics[position]
}
override fun getItemCount(): Int {
return topics.size
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var topicTextView: TextView = itemView.findViewById(R.id.gir_topics_tvid)
}
}
Please use these constructors:
class CustomRecyclerAdapter constructor(
val context: Context,
val topics: Array<String> = emptyArray(),
private var alist: ArrayList<String>
) : RecyclerView.Adapter<CustomRecyclerAdapter.ViewHolder>() {
constructor(context: Context, sss: ArrayList<String>) : this(context, alist = sss)
// ...
}
In the primary constructor you need to specify a default value for topics. Also add context: Context parameter to the secondary constructor and pass it to this() when calling the primary constructor.

Android/Kotlin, how to reduce duplicated part of this code

I'm a library author and have to intercept all touch events of child views, by overriding ViewGroup.onInterceptTouchEvent().
First I wrote the following code (simplified):
interface touchIntercepter {
// my library set this field to intercept touch event
var touchHandler: ((MotionEvent) -> Boolean)?
}
class LinearLayoutTouchIntercepter #JvmOverloads constructor (
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
defStyleRes: Int = 0
)
: touchIntercepter
, LinearLayout(context, attrs, defStyleAttr, defStyleRes)
{
override var touchHandler: ((MotionEvent) -> Boolean)? = null
override fun onInterceptTouchEvent(event: MotionEvent) = touchHandler?.invoke(event) ?: false
}
Library users can use the LinearLayoutTouchInterceptor in their layout xml file instead of standard LinearLayout and then my library code can intercept touch event of the user layout's child views by touchIntercepter interface.
I think it is wonderful if there's something like ViewGroup.setOnInterceptTouchListener(), like View.setOnClickListener(), but I found that there isn't.
Now the problem is, I want to provide the same functionality for RelativeLayout, FrameLayout and other ViewGroup descendants.
For example,
class RelativeLayoutTouchIntercepter #JvmOverloads constructor (
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
defStyleRes: Int = 0
)
: touchIntercepter
, RelativeLayout(context, attrs, defStyleAttr, defStyleRes)
{
override var touchHandler: ((MotionEvent) -> Boolean)? = null
override fun onInterceptTouchEvent(event: MotionEvent) = touchHandler?.invoke(event) ?: false
}
As you can see, all code is the same but the only difference is inheriting XXXXXLayout instead of LinearLayout. I don't want to copy and paste them but have no idea how to reduce the duplication.
It seems that Kotlin generics are not helping in this case while C++ template perfectly can help like this pseudo code :
template <typename T>
class TouchInterceptorTmpl : public T
{
void onInterceptTouchEvent() override;
};
using RelativeLayoutTouchInterceptor = TouchInterceptorTmpl<RelativeLayout>;
using FrameLayoutTouchInterceptor = TouchInterceptorTmpl<FrameLayout>;
No way to do like this in Kotlin?
You can reduce duplication a little bit by making a concrete implementation of your interface and using it as a delegate. Unfortunately, you can't avoid overriding onInterceptTouchEvent in each implementation due to how inheritance works, but you can make an extension function for your interface to shorten that code a bit.
Note, interface names in Kotlin are capitalized by convention.
Setup:
interface TouchInterceptor {
var touchInterceptionHandler: ((MotionEvent) -> Boolean)?
}
class TouchInterceptorImpl: TouchInterceptor {
override var touchInterceptionHandler: ((MotionEvent) -> Boolean)? = null
}
fun TouchInterceptor.intercept(event: MotionEvent): Boolean = touchInterceptionHandler?.invoke(event) ?: false
Usage:
class RelativeLayoutTouchIntercepter #JvmOverloads constructor (
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
defStyleRes: Int = 0
)
: TouchInterceptor by TouchInterceptorImpl()
, RelativeLayout(context, attrs, defStyleAttr, defStyleRes)
{
override fun onInterceptTouchEvent(event: MotionEvent): Boolean = intercept(event)
}

how to set property as val at customview written by xml

In case of below, MyView's callback is not mutable property so I want to declare as val.
Not only that, Since it can not be initialized at init function, it is error prone.
How to set callback as val or... how to set callback in the init function?
Thanks
class MyView(context: Context, attributeSet: AttributeSet) : View(context, attributeSet) {
lateinit var callback: Callback
init {
//callback =
}
}
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
setContentView(R.layout.main_activity)
my_view.callback = object : Callback {
}
}
}
<LinearLayout>
<com.example.MyView
android:id="#+id/my_view/>
</LinearLayout>
If you want to set callback from another class, e.g. Activity, this property must be mutable:
class MyView(context: Context, attributeSet: AttributeSet) : View(context, attributeSet) {
var callback: Callback? = null
}
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
setContentView(R.layout.main_activity)
my_view.callback = object : Callback {
}
}
}
If you want callback to be immutable you should initialize it in MyView class:
class MyView(context: Context, attributeSet: AttributeSet) : View(context, attributeSet) {
val callback: Callback = object : Callback {
}
}
But in this case you won't be able to update it from another class.

Write a test cases for custom view

A custom view contains several attributes as property, to make this code testable than added unit test (RobolectricTestRunner).
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
inflateView()
bindView()
initialize()
setContentHeight()
}
}
private fun inflateView() {
View.inflate(context, R.layout.view_custom, this)
}
private fun bindView() {
panelView = findViewById(R.id.panel_view)
}
private fun setContentHeight(height: Int) {
layoutParams.height = height <-- test case fail due to layoutParams null.
requestLayout()
}
TestCase
#Test
fun init() {
val activity = Robolectric.buildActivity(Activity::class.java).create().get()
var context = mock(Context::class.java)
var typedArray = mock(TypedArray::class.java)
var dropDownView = DropdownTextView(activity, mock(AttributeSet::class.java))
}
layoutParams having a null object and mock not work here.

Categories

Resources