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()
}
Related
I am trying to set up a canvas to redraw whenever the user clicks a button. Whenever I trigger the redraw via invalidate() the app freezes and crashes. I am very new to android development so I am struggling to see what I neglecting. Any insight would be appriciated.
The relevent classes are included below.
Thanks,
mainactivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
fun submit(view: View) {
val text = findViewById<View>(R.id.editTextNumberDecimal) as EditText
val canvas = findViewById<View>(R.id.customCanvas) as CustomCanvas
val value = text.text.toString()
val t = findViewById<View>(R.id.textView) as TextView
t.text = value
canvas.setWeightandRedraw(value.toFloat())
}
}
canvas
class CustomCanvas : View {
constructor(context: Context) : this(context, null)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
}
var weight: Float = 0.0f;
override fun onDraw(canvas: Canvas?) {
drawBar(canvas);
drawWeightKg(canvas, this.weight)
}
fun setWeightandRedraw(_weight: Float) {
weight = _weight;
invalidate();
}
fun drawBar(canvas: Canvas?) {
.........
}
fun drawWeightKg(canvas: Canvas?, weight: Float) {
.........
}
}
Moving invalidate() to onDraw() appeard to fix this.
override fun onDraw(canvas: Canvas?) {
drawBar(canvas);
drawWeightKg(canvas, this.weight)
invalidate();
}
How to save scroll state of scrollview properly.In my code, I'm using :
scroll_x = scrollView.getScrollX();
scroll_y = scrollView.getScrollY();
when activity pause,i'm stored x and y as you can see here, and when activity start, i'm scroll scrollView to x and y.
But crux is (main problem) is, scrollview not scrollview to x and y properly, it scroll up or down a little bit automatically. How to fix it?
You can manage the instance state by using this class:
class SaveScrollNestedScrollViewer : NestedScrollView {
constructor(context: Context) : super(context)
constructor(context: Context, attributes: AttributeSet) : super(context, attributes)
constructor(context: Context, attributes: AttributeSet, defStyleAttr: Int) : super(context, attributes, defStyleAttr)
public override fun onSaveInstanceState(): Parcelable? {
return super.onSaveInstanceState()
}
public override fun onRestoreInstanceState(state: Parcelable?) {
super.onRestoreInstanceState(state)
}
}
use on your xml:
<yourClassNamePlace.SaveScrollNestedScrollViewer
android:id="#+id/my_scroll_viewer"
android:layout_width="match_parent"
android:layout_height="match_parent">
</yourClassNamePlace.SaveScrollNestedScrollViewer>
and then use in activity like this:
class MyActivity : AppCompatActivity() {
companion object {
var myScrollViewerInstanceState: Parcelable? = null
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.my_activity)
if (myScrollViewerInstanceState != null) {
my_scroll_viewer.onRestoreInstanceState(myScrollViewerInstanceState)
}
}
public override fun onPause() {
super.onPause()
myScrollViewerInstanceState = my_scroll_viewer.onSaveInstanceState()
}
}
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()
}
I have used a custom toolbar class so i can align the title to the right and every thing works fine except the navigation back icon is not vertically aligned
and this is the custom toolbar class
class RTLToolbar #JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : Toolbar(context, attrs, defStyleAttr) {
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
super.onLayout(changed, l, t, r, b)
val childCount = childCount
for (i in 0 until childCount) {
val view = this.getChildAt(i)
if (view is TextView) {
forceTitleCenter(view,l, r)
break
}
}
}
private fun forceTitleCenter(view: TextView, l: Int, r: Int) {
val top = view.top
val bottom = view.bottom
view.layout(l, top, r, bottom)
navigationIcon?.let{ view.setPadding(it.intrinsicWidth,0,0,0) }
view.gravity = Gravity.RIGHT
}
}
i found this xml attribute
app:buttonGravity="center_vertical"
and it did the job, now the back icon is aligned with the title
I want to use a centered spinner where the width of the spinner is only as wide as the selected item text. From my research it seems that this is not natively supported out of the box with an attribute so I found another StackOverflow question/answer and tried implementing that but ran into some issues with it.
So I took option 1 from this SO response and implemented it in Kotlin and It's not working for me
class DynamicWidthSpinner #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : AppCompatSpinner(context, attrs, defStyleAttr) {
override fun setAdapter(adapter: SpinnerAdapter?) {
super.setAdapter(if (adapter != null) WrapperSpinnerAdapter(adapter) else null)
}
inner class WrapperSpinnerAdapter(val baseAdapter: SpinnerAdapter) : SpinnerAdapter {
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
return baseAdapter.getView(selectedItemPosition, convertView, parent)
}
override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View {
return baseAdapter.getDropDownView(position, convertView, parent)
}
override fun getCount(): Int = baseAdapter.count
override fun getItem(position: Int): Any = baseAdapter.getItem(position)
override fun getItemId(position: Int): Long = baseAdapter.getItemId(position)
override fun getItemViewType(position: Int): Int = baseAdapter.getItemViewType(position)
override fun getViewTypeCount(): Int = baseAdapter.viewTypeCount
override fun hasStableIds(): Boolean = baseAdapter.hasStableIds()
override fun isEmpty(): Boolean = baseAdapter.isEmpty
override fun registerDataSetObserver(observer: DataSetObserver) {
baseAdapter.registerDataSetObserver(observer)
}
override fun unregisterDataSetObserver(observer: DataSetObserver) {
baseAdapter.unregisterDataSetObserver(observer)
}
}
}
and in my MainActivity I'm doing this from onCreate
val spinner: DynamicWidthSpinner = findViewById(R.id.global_toolbar_location_spinner)
val tempLocationList = ArrayList<String>()
tempLocationList.add("Test1")
tempLocationList.add("Much longer test string 2")
spinner.adapter = ArrayAdapter(
this,
R.layout.global_toolbar_spinner_item,
tempLocationList
)
spinner.onItemSelectedListener = object : OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, arg3: Long) {
// TODO: do stuff on selection here
}
override fun onNothingSelected(arg0: AdapterView<*>) {
// TODO: do nothing... yet
}
}
spinner.setSelection(0)
and I am using my custom Spinner in the layout xml (ommitting everything else that is not necessary because I am able to get it work just fine using the native <Spinner> or androidx compat Spinner
<com.blablabla.app.ui.DynamicWidthSpinner
android:id="#+id/global_toolbar_location_spinner"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:spinnerMode="dialog"
/>
What I see is just the first item "Test1" and nothing happens when I tap on it and arrow seems to have disappeared now as well
I figured out the issue. Turns out that this is one of those cases where the #JvmOverloads doesn't work. Once I converted it to the multiple constructor kotlin syntax it worked without a problem
class DynamicWidthSpinner : AppCompatSpinner {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
...
}