Intro
In one of my project I tried to create custom EditText with header and some custom validations. I came into a strange problem when I tested this custom view with screen rotation and activity recreation.
What is problem
Before recreation
When app starts all edit text have correct values which were set statically from activity. As on picture bellow:
After recreation
After I rotate screen or recreate activity EditText's values will be messed up. CustomEditText values are set to value of last edit text in XML. Simple (Basic Android EditText) edit text values are set normally.
Codes
I copied codes from project where this problem occurs.
MainActivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
first_custom_edit_text.header = "First header"
first_custom_edit_text.setText("First text")
third_custom_edit_text.header = "Third header"
third_custom_edit_text.setText("Third text")
first_simple_edit_text.setText("First simple - Not affected")
second_custom_edit_text.header = "Second header"
second_custom_edit_text.setText("Second text")
second_simple_edit_text.setText("Second simple - Not affected")
}
}
CustomEditText
class CustomEditText : LinearLayout {
fun setText(value: String?){
this.input_edit_text.text = Editable.Factory.getInstance().newEditable(value ?: "")
}
fun getText(): String {
return this.input_edit_text.text.toString()
}
var header: String?
get() = this.header_text_view.text.toString()
set(value) {
this.header_text_view.text = Editable.Factory.getInstance().newEditable(value ?: "")
}
constructor(context: Context) : super(context){
init(context, null)
}
constructor(context: Context, attrs: AttributeSet) : super(context, attrs){
init(context, attrs)
}
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
init(context, attrs)
}
private fun init(context: Context, attrs: AttributeSet?) {
inflate(context, R.layout.ui_custom_edit_text, this)
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:orientation="vertical">
<com.example.customedittextbug.CustomEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/first_custom_edit_text"/>
<com.example.customedittextbug.CustomEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/second_custom_edit_text"/>
<EditText
tools:hint="input#hint.example"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="-4dp"
android:layout_marginRight="-4dp"
android:textColor="#android:color/black"
android:textSize="18sp"
android:inputType="text"
android:id="#+id/first_simple_edit_text"/>
<com.example.customedittextbug.CustomEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/third_custom_edit_text"/>
<EditText
tools:hint="input#hint.example"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="-4dp"
android:layout_marginRight="-4dp"
android:textColor="#android:color/black"
android:textSize="18sp"
android:inputType="text"
android:id="#+id/second_simple_edit_text"/>
</LinearLayout>
ui_custom_edit_text.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
tools:text="Input header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#android:color/black"
android:textStyle="bold"
android:textSize="17sp"
android:id="#+id/header_text_view"/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:id="#+id/validations_errors_holder"/>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/common_input_holder">
<EditText
tools:hint="input#hint.example"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="-4dp"
android:layout_marginRight="-4dp"
android:textColor="#android:color/black"
android:textSize="18sp"
android:inputType="text"
android:id="#+id/input_edit_text"/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignEnd="#+id/input_edit_text"
android:layout_centerVertical="true"
android:layout_marginEnd="4dp"
android:layout_marginStart="4dp"
android:gravity="end"
android:orientation="horizontal"
android:id="#+id/right_view_holder"/>
</RelativeLayout>
</LinearLayout>
UPDATE
I found those two guides with nice explanation how to fix this problem after my question was answered.
Link1, Link2
State restoration is keyed by ID, and all of your custom views have a sub-View with the same ID: input_edit_text. Thus, they all get restored to the same state because they all got the last one that was saved under that ID.
You could avoid this by setting android:saveEnabled="false" on that EditText (though you'll probably want to do the save/restore of instance state yourself in your CustomEditText).
I was tired of searching but this worked for me.
add to CustomEditText class
companion object {
private const val SPARSE_STATE_KEY = "SPARSE_STATE_KEY"
private const val SUPER_STATE_KEY = "SUPER_STATE_KEY"
}
override fun dispatchSaveInstanceState(container: SparseArray<Parcelable>) {
dispatchFreezeSelfOnly(container)
}
override fun dispatchRestoreInstanceState(container: SparseArray<Parcelable>) {
dispatchThawSelfOnly(container)
}
override fun onSaveInstanceState(): Parcelable? {
Log.i("ByHand", "onSaveInstanceState")
return Bundle().apply {
Log.i("ByHand", "Writing children state to sparse array")
putParcelable(SUPER_STATE_KEY, super.onSaveInstanceState())
putSparseParcelableArray(SPARSE_STATE_KEY, saveChildViewStates())
}
}
override fun onRestoreInstanceState(state: Parcelable?) {
Log.i("ByHand", "onRestoreInstanceState")
var newState = state
if (newState is Bundle) {
Log.i("ByHand", "Reading children children state from sparse array")
val childrenState = newState.getSparseParcelableArray<Parcelable>(SPARSE_STATE_KEY)
childrenState?.let { restoreChildViewStates(it) }
newState = newState.getParcelable(SUPER_STATE_KEY)
}
super.onRestoreInstanceState(newState)
}
fun ViewGroup.saveChildViewStates(): SparseArray<Parcelable> {
val childViewStates = SparseArray<Parcelable>()
children.forEach { child -> child.saveHierarchyState(childViewStates) }
return childViewStates
}
fun ViewGroup.restoreChildViewStates(childViewStates: SparseArray<Parcelable>) {
children.forEach { child -> child.restoreHierarchyState(childViewStates) }
}
this link for details
Related
I wrote a small custom text view in order to use it on a list. There are three States depending on which i want to have this:
State 1 -> just the text, for example TEST_TEXT
State 2 -> a drawable start, a color background and the same text TEST_TEXT
State 3 -> a different drawable start, different color background and the same text TEST_TEXT
The text has always the same value (TEST_TEXT)
But with my implementation i lost the alignment and as a result the text on the state that
i do not have a drawable start is not align with the others that have drawable as you can see on the image below. I want all of them to be start align.
Is there a way to achieve it?
My custom text view class is :
enum class State {
STATE_ONE,
STATE_TWO,
STATE_THREE
}
class CustomTextView #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
): TextView(context, attrs, defStyleAttr) {
private val PADDING = 4 * resources.displayMetrics.density
private val ZERO_PADDING = 0 * resources.displayMetrics.density
var state: State = State.STATE_ONE
set(value) {
field = value
updateTextView()
invalidate()
}
private fun updateTextView() {
when(state) {
State.STATE_TWO -> {
this.setBackgroundResource(R.color.light_red)
this.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_baseline_message_24, 0, 0, 0)
this.compoundDrawablePadding = PADDING.toInt()
this.setPadding(PADDING.toInt(), PADDING.toInt(), PADDING.toInt(), PADDING.toInt())
}
State.STATE_THREE -> {
this.setBackgroundResource(R.color.colorGreenBright)
this.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_baseline_message_24, 0, 0, 0)
this.compoundDrawablePadding = PADDING.toInt()
this.setPadding(PADDING.toInt(), PADDING.toInt(), PADDING.toInt(), PADDING.toInt())
}
else -> {
this.setBackgroundResource(R.color.transparent)
this.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null)
this.compoundDrawablePadding = PADDING.toInt()
this.setPadding(ZERO_PADDING.toInt(), ZERO_PADDING.toInt(), ZERO_PADDING.toInt(), ZERO_PADDING.toInt())
}
}
}
}
The layout where i add them is this:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto">
<custom.textView.CustomTextView
android:id="#+id/textViewOne"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TEST_TEXT"
android:textSize="12sp"
android:textColor="#color/black"
android:background="#drawable/drawable_rounded_text_bg"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="30dp"/>
<custom.textView.CustomTextView
android:id="#+id/textViewTwo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TEST_TEXT"
android:textSize="12sp"
android:textColor="#color/black"
android:background="#drawable/drawable_rounded_text_bg"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="#+id/textViewOne"
android:layout_marginTop="10dp"/>
<custom.textView.CustomTextView
android:id="#+id/textViewThree"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TEST_TEXT"
android:textSize="12sp"
android:textColor="#color/black"
android:background="#drawable/drawable_rounded_text_bg"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="#+id/textViewTwo"
android:layout_marginTop="10dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>
and finally my activity is simple like this:
class CustomTextActivity: BaseActivity() {
private lateinit var binding: ActivityCustomTextBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(getView())
initLayout()
}
override fun getView(): View {
binding = ActivityCustomTextBinding.inflate(layoutInflater)
return binding.root
}
private fun initLayout() {
binding.textViewOne.state = State.STATE_ONE
binding.textViewTwo.state = State.STATE_TWO
binding.textViewThree.state = State.STATE_THREE
}
}
To my mind, if you want to have the same alignment, even without inner drawables, you have 2 options:
right align the text
display a "fake drawable" in the first case
I have this compound view ArticleView,
When I replace the root LinearLayout with merge the view is not visible on my test device, nor the emulator. Why does this happen?
ArticleView.kt:
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.LinearLayout
import com.kaf.hajjcompanion.R
import kotlinx.android.synthetic.main.article_view.view.*
import net.cachapa.expandablelayout.ExpandableLayout.State.EXPANDED
class ArticleView #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : LinearLayout(context, attrs, defStyleAttr) {
private val header by lazy { question_header }
private val paragraph by lazy { paragraph_view }
private val expandableParagraph by lazy { expandable_paragraph }
init {
inflate()
}
private fun inflate() {
LayoutInflater.from(context).inflate(R.layout.article_view, this, true)
header.setOnClickListener {
expandableParagraph.toggle()
}
expandableParagraph.setOnExpansionUpdateListener { _, state ->
header.setCompoundDrawablesRelativeWithIntrinsicBounds(
0, 0,
if (state == EXPANDED) R.drawable.ic_expand_less_white_24dp else R.drawable.ic_expand_more_white_24dp,
0
)
}
}
fun render(viewState: ArticleViewState) {
with(viewState) {
header.setText(title)
paragraph.text = paragraphText
}
}
}
article_view.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="#dimen/media_width"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatTextView
android:id="#+id/question_header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/hajj_black"
android:drawableEnd="#drawable/ic_expand_more_white_24dp"
android:elevation="#dimen/default_elevation"
android:padding="8dp"
android:textAlignment="viewStart"
android:textColor="#android:color/white"
android:textSize="#dimen/entry_title_size"
tools:text="#string/hajj_comp_how_to"
/>
<net.cachapa.expandablelayout.ExpandableLayout
android:id="#+id/expandable_paragraph"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:el_duration="500"
app:el_expanded="false">
<com.kaf.hajjcompanion.ui.commons.EmbeddedParagraphView
android:id="#+id/paragraph_view"
android:layout_width="match_parent"
android:layout_height="#dimen/paragraph_height"/>
</net.cachapa.expandablelayout.ExpandableLayout>
</LinearLayout>
In my fragment:
private fun renderState(state: SectionState) {
when (state) {
//..
is SectionState.Article -> ArticleView(context!!).run {
render(state.what)
root.addView(this)
}
//..
}
}
fragment:
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="#dimen/media_width"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_gravity="center"
android:scrollbars="none">
<LinearLayout
android:id="#+id/root"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:divider="#drawable/entries_divider"
android:orientation="vertical"
android:showDividers="middle"/>
</ScrollView>
please note that I'm not using a RecyclerView because I'm dealing with static data that are less than 5 elements.
It's probably because you're losing your root parameters:
android:layout_width="#dimen/media_width"
android:layout_height="match_parent"
android:orientation="vertical"
therefore:
The size of added ArticleViews is determined by the the generateDefaultLayoutParams() of the enclosing layout.
The orientation is the default orientation of the LinearLayout which (if I'm not mistaken) is the horizontal.
I have checked many answers to find my issue however I was not successful. I have an activity that holds a compound drawable.
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.my.profile.widgets.ProfileWidget
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
....
</LinearLayout>
</layout>
This is my ProfileWidget:
class ProfileWidget #JvmOverloads constructor(context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : LinearLayout(context, attrs, defStyleAttr) {
#Inject lateinit var viewModel: ProfileWidgetViewData
#Inject lateinit var viewActions: ProfileWidgetActions
private val binding: WidgetProfileBinding = DataBindingUtil.inflate(
LayoutInflater.from(context), R.layout.widget_profile, this, true)
// private val binding = WidgetProfileBinding.inflate(LayoutInflater.from(context), this, true)
override fun onAttachedToWindow() {
super.onAttachedToWindow()
setupDependencyInjection()
setupDataBinding()
viewActions.testUI()
}
private fun setupDependencyInjection() {
(context as ProfileActivity).getProfileComponent()?.inject(this)
}
private fun setupDataBinding() {
binding.viewModel = viewModel
}
}
This is its layout:
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="android.view.View" />
<variable
name="viewModel"
type="com.my.profile.widgets.ProfileWidgetViewData" />
</data>
<LinearLayout
android:id="#+id/profilesContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="#FF0000"
>
<TextView
android:layout_width="match_parent"
android:layout_height="48dp"
android:text="profile 1"
android:visibility="#{viewModel.textView_1.get() ? View.VISIBLE : View.INVISIBLE}"/>
<TextView
android:layout_width="match_parent"
android:layout_height="48dp"
android:text="profile 2"
android:visibility="#{viewModel.textView_2.get() ? View.VISIBLE : View.INVISIBLE}"/>
</LinearLayout>
</layout>
Finally my ViewModel class supposed to make TextViews
visible/invisible.
interface ProfileWidgetViewData {
val textView_1: ObservableBoolean
val textView_2: ObservableBoolean
}
interface ProfileWidgetActions {
fun testUI()
}
class ProfileWidgetViewModelImpl : ProfileWidgetViewData, ProfileWidgetActions {
override val textView_1 = ObservableBoolean(false)
override val textView_2 = ObservableBoolean(false)
override fun testUI() {
setProfilesContainerVisibility(true)
setAddProfileContainerVisibility(true)
}
private fun setProfilesContainerVisibility(isVisible: Boolean) {
textView_1.set(isVisible)
}
private fun setAddProfileContainerVisibility(isVisible: Boolean) {
textView_2.set(isVisible)
}
}
Unfortunately I don't see anything wrong in above codes. When I launch
the app, those two TextView are Invisible although I have set them to be visible.
Check below is added or not in build.gradle(obviously you already added)
apply plugin: 'kotlin-kapt'
android {
dataBinding {
enabled = true
}
}
dependencies {
kapt "com.android.databinding:compiler:3.1.3"
}
And add below line in your xml file for visibility or invisible
<TextView
android:layout_width="match_parent"
android:layout_height="48dp"
android:text="profile 1"
android:visibility="#{safeUnbox(viewModel.textView_1) ? View.VISIBLE : View.INVISIBLE}"/>
This is my layout file for the custom view
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="?actionBarSize">
<ImageView
android:id="#+id/ivMenu"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginBottom="8dp"
android:layout_marginTop="8dp"
android:background="#drawable/ripple"
android:clickable="true"
android:padding="12dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="#drawable/ic_menu_gray" />
<ImageView
android:id="#+id/ivDrawer"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginBottom="8dp"
android:layout_marginTop="8dp"
android:background="#drawable/ripple"
android:clickable="true"
android:padding="12dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="#drawable/ic_drawer_gray" />
</android.support.constraint.ConstraintLayout>
</LinearLayout>
There are two ImageViews that I need to set click listeners on. I have created a class extending LinearLayout, since it is the root element in my layout.
I don't know how to set click listeners on individual items in the layout.
here is my class file for the layout
class WolfBottomAppBar : LinearLayout {
constructor(context: Context) : super(context) {
init(context)
}
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
init(context)
}
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
init(context)
}
private fun init(context: Context) {
((context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)) as LayoutInflater).inflate(R.layout.wolf_bottom_app_bar, this)
}
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
if (event.action == MotionEvent.ACTION_UP) {
/*
* I can set click listener for the whole view here, but not on individual items
*/
}
return super.dispatchTouchEvent(event)
}
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
if (event.action == KeyEvent.ACTION_UP && (event.keyCode == KeyEvent.KEYCODE_DPAD_CENTER || event.keyCode == KeyEvent.KEYCODE_ENTER)) {
}
return super.dispatchKeyEvent(event)
}
}
put
fun setMenuListener(listener : View.OnClickListener) {
findViewById<ImageView>(R.id.ivMenu).setOnClickListener(listener)
}
fun setDrawerListener(listener : View.OnClickListener) {
findViewById<ImageView>(R.id.ivDrawer).setOnClickListener(listener)
}
into you custom view class.
You can just access the ImageViews with their ids. You need to add the inflated view to your root (=WolfBottomAppBar); e.g. in the init method:
private fun init(context: Context) {
((context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)) as LayoutInflater).inflate(R.layout.wolf_bottom_app_bar, this, true)
ivMenu.setOnClickListener { Log.d(TAG, "Image ivMenu clicked!") }
}
Don't forget to import the synthetic Kotlin package for the layout file and to add android:focusable="true" to the two ImageViews xml
I'm trying to create an DSL-like OnClick-listener (in kotlin) for a custom view that I'm using several places in the Android project I'm currently working on. The view has an ImageView, a primary textview and a secondary textview. I'm trying to create a listener-helper that allows you to only override specific methods of an interface instead of all of them (inspired by this article). But I can't get it working. Actually it doesn't even work using regular OnClick listeners.
Here's my ErrorMessageView:
class ErrorMessageView #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyle: Int = 0,
defStyleRes: Int = 0
) : FrameLayout(context, attrs, defStyle, defStyleRes) {
private var mOnClickListener: OnErrorMessageViewClickListener? = null
init {
LayoutInflater.from(context).inflate(
R.layout.custom_errorview,
this,
true)
ButterKnife.bind(this)
}
interface OnErrorMessageViewClickListener {
fun onImageClick()
fun onPrimaryTextClick()
fun onSecondaryTextClick()
}
... left out for brevity ...
fun setOnErrorMessageViewClickListener(
onViewClickListener: OnErrorMessageViewClickListener?) {
this.mOnClickListener = onViewClickListener
}
fun setOnErrorMessageViewClickListener(init: ErrorMessageViewClickListenerHelper.() -> Unit) {
val listener = ErrorMessageViewClickListenerHelper()
listener.init()
mOnClickListener = listener
}
#OnClick(R.id.image_container)
internal fun onImageViewClick() {
Timber.d("Clicked image view")
mOnClickListener?.onImageClick()
}
#OnClick(R.id.primary_text_container)
internal fun onPrimaryTextViewClick() {
Timber.d("Clicked primary textview")
mOnClickListener?.onPrimaryTextClick()
}
#OnClick(R.id.secondary_text_container)
internal fun onSecondaryTextViewClick() {
Timber.d("Clicked secondary textview")
mOnClickListener?.onSecondaryTextClick()
}
}
And here's my helper class:
private typealias ErrorViewClickListener = () -> Unit
open class ErrorMessageViewClickListenerHelper : ErrorMessageView.OnErrorMessageViewClickListener {
private var mImageClick: ErrorViewClickListener? = null
fun onImageClick(onImageClick: ErrorViewClickListener?) {
mImageClick = onImageClick
}
override fun onImageClick() {
mImageClick?..invoke()
}
private var mPrimaryTextClick: ErrorViewClickListener? = null
fun onPrimaryTextClick(onPrimaryTextClick: ErrorViewClickListener?) {
mPrimaryTextClick = onPrimaryTextClick
}
override fun onPrimaryTextClick() {
mPrimaryTextClick?.invoke()
}
private var mSecondaryTextClick: ErrorViewClickListener? = null
fun onSecondaryTextClick(onSecondaryTextClick: ErrorViewClickListener?) {
mSecondaryTextClick = onSecondaryTextClick
}
override fun onSecondaryTextClick() {
mSecondaryTextClick?.invoke()
}
}
My layout:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginEnd="24dp">
<android.support.v7.widget.AppCompatImageView
android:id="#+id/image_container"
android:layout_width="#dimen/dialog_worklist_image_size"
android:layout_height="#dimen/dialog_worklist_image_size"
android:layout_centerVertical="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:ignore="ContentDescription" />
<TextView
android:id="#+id/primary_text_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginEnd="24dp"
android:layout_marginStart="24dp"
android:layout_marginTop="8dp"
android:textSize="16sp"
android:clickable="true"
android:focusable="true"
app:layout_constraintEnd_toEndOf="#+id/image_container"
app:layout_constraintStart_toStartOf="#+id/image_container"
app:layout_constraintTop_toBottomOf="#id/image_container" />
<TextView
android:id="#+id/secondary_text_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginEnd="24dp"
android:layout_marginStart="24dp"
android:textSize="16sp"
android:clickable="true"
android:focusable="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/primary_text_container" />
</android.support.constraint.ConstraintLayout>
I'm using the helper in my code like this, but nothing gets logged (It's almost as if no clicklistener can be attached):
override fun setWorklistNotEnabledMessage(showMessage: Boolean) {
if (showMessage) {
mView?.dialog_worklist_recyclerview?.visibility = View.GONE
mView?.dialog_worklist_errorview?.apply {
visibility = View.VISIBLE
setSecondaryTextClickListener(View.OnClickListener { Timber.d("Test secondary click") })
setErrorDrawable(R.drawable.ic_worklist_disabled_black_24dp)
setPrimaryText(R.string.global_worklist_disabled_error)
setSecondaryText(R.string.dialog_worklist_worklist_disabled_error_secondary_text)
setOnErrorMessageViewClickListener {
onSecondaryTextClick { Timber.d("Test secondary click") }
onPrimaryTextClick { Timber.d("Test primary click") }
onImageClick { Timber.d("Test image click") }
// . this#WorklistDialog
}
}
} else {
mView?.dialog_worklist_errorview?.apply {
visibility = View.GONE
setErrorDrawable(null)
setPrimaryText("")
setSecondaryText("")
setOnErrorMessageViewClickListener(null)
}
}
}
Does anyone have a suggestion to what is wrong with my code?
Btw. the above code is used in a DialogFragment - that is why you might notice the mView?. as a bit awkward. But that is the way to get a handle on the views in a dialog.
Was just about to start crying when I finally found the solution - OMG.
The problem was the SwipeRefreshLayout in the layout file for the dialogfragment, which can be seen here:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<android.support.v7.widget.Toolbar
android:id="#+id/dialog_worklist_toolbar"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:background="#color/colorPrimary"
android:elevation="4dp"
android:minHeight="?android:attr/actionBarSize"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar" />
<com.conhea.smartgfr.ui.examination.layouts.ErrorMessageView
android:id="#+id/dialog_worklist_errorview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/dialog_worklist_toolbar" />
<ProgressBar
android:id="#+id/dialog_worklist_progressbar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/dialog_worklist_toolbar" />
<android.support.v4.widget.SwipeRefreshLayout
android:id="#+id/dialog_worklist_swiperefreshlayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
app:layout_constraintTop_toBottomOf="#id/dialog_worklist_toolbar">
<android.support.v7.widget.RecyclerView
android:id="#+id/dialog_worklist_recyclerview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#android:color/transparent" />
</android.support.v4.widget.SwipeRefreshLayout>
</android.support.constraint.ConstraintLayout>
The SwipeRefreshLayout was blocking the custom ErrorMessageView from being clickable.
After adding mView?.dialog_worklist_swiperefreshlayout?.isEnabled = false to the below in my DialogFragment everything started working:
override fun setWorklistNotEnabledMessage(showMessage: Boolean) {
if (showMessage) {
mView?.dialog_worklist_recyclerview?.visibility = View.GONE
mView?.dialog_worklist_swiperefreshlayout?.isEnabled = false
mView?.dialog_worklist_errorview?.apply {
visibility = View.VISIBLE
setErrorDrawable(R.drawable.ic_worklist_disabled_black_24dp)
setPrimaryText(R.string.global_worklist_disabled_error)
setSecondaryText(R.string.dialog_worklist_worklist_disabled_error_secondary_text)
setOnErrorMessageViewClickListener {
onSecondaryTextClick { Timber.d("Test secondary click") }
onPrimaryTextClick { Timber.d("Test primary click") }
onImageClick { Timber.d("Test image click") }
}
}
} else {
mView?.dialog_worklist_errorview?.apply {
visibility = View.GONE
setErrorDrawable(null)
setPrimaryText("")
setSecondaryText("")
setOnErrorMessageViewClickListener(null)
}
}
}