I have a question about how the visibility of an object in the ViewModel will change.
I'll tell you my case: I have a login interface that has two Edittexts and two buttons, a button and an Edittext are invisible by default, and I want the button that is visible to make the first Edittext and the button that I clicked invisible and make the second button and the second Edittext visible. And here's the problem, I could do all this in Activity, but I need to do it in ViewModel and I have no idea how to access the xml components from there.
I know all this is messy so I'll send the classes and if someone could tell me how to do this I'd appreciate it. Thank you. Thank you.
Login XML:
<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>
<variable
name="viewModel"
type="com.quobis.sippo.ecco.viewmodel.LoginViewModel"/>
</data>
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="LoginUser">
<ImageView
android:layout_width="250dp"
android:layout_height="200dp"
android:src="#drawable/logom"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.2"
/>
<EditText
android:id="#+id/usr"
android:layout_width="200dp"
android:layout_height="50dp"
android:hint="#string/hint_user"
android:textSize="18sp"
android:textColorPrimary="#color/colorLetterLogin"
android:backgroundTint="#color/colorBackButtLogin"
android:elevation="20dp"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginLeft="60dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.6"
app:addTextChangedListener="#{viewModel.emailTextWatcher}"
/>
<Button
android:id="#+id/btn_usr"
android:layout_width="55dp"
android:layout_height="50dp"
android:background="#color/colorBackButtLogin"
android:drawableBottom="#drawable/ic_keyboard_arrow_right"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginLeft="260dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.6"
android:onClick="#{viewModel::onUserClicked}"
/>
<EditText
android:id="#+id/pass"
android:layout_width="200dp"
android:layout_height="50dp"
android:hint="#string/hint_pass"
android:textSize="18sp"
android:shape="rectangle"
android:inputType="textPassword"
android:textColorPrimary="#color/colorLetterLogin"
android:backgroundTint="#color/colorBackButtLogin"
android:elevation="20dp"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginLeft="60dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.6"
android:visibility="invisible"
android:onClick="#{viewModel::onLoginClicked}"
/>
<Button
android:id="#+id/btn_pass"
android:layout_width="55dp"
android:layout_height="50dp"
android:background="#color/colorBackButtLogin"
android:drawableBottom="#drawable/ic_keyboard_arrow_right"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginLeft="260dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.6"
android:visibility="invisible"
/>
<Spinner
android:id="#+id/spinner_usr"
android:layout_width="120dp"
android:layout_height="40dp"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginLeft="40dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.85"
/>
</android.support.constraint.ConstraintLayout>
</layout>
ViewModel:
class LoginViewModel(private val listener: LoginResultCallbacks) :
ViewModel() {
private val user: UserModel
var userp ="jorge"
init {
this.user= UserModel(email = "")
}
fun emailTextWatcher(): TextWatcher {
return object : TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
user.setEmail(s.toString())
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
}
override fun afterTextChanged(s: Editable) {
}
}
}
fun onUserClicked(v: View) {
}
fun onLoginClicked(v:View) {
if (user.getEmail() == userp)
listener.onSucces("Correcto")
else
listener.onError("Fallo")
}
}
Note: the method to change the visibility would be the onUserClicked.
Main activity:
class LoginUser : AppCompatActivity(), LoginResultCallbacks {
lateinit var binding: ActivityLoginUserBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_login_user)
binding.viewModel = ViewModelProviders.of(this, LoginViewModelFactory(this)).get(LoginViewModel::class.java)
var languages = arrayOf("English", "EspaƱol", "Galego")
val spinner = binding.spinnerUsr
if (spinner != null) {
val arrayAdapter = ArrayAdapter(this, R.layout.spinner_item, languages)
spinner.adapter = arrayAdapter
}
}
override fun onSucces(message: String) {
Toast.makeText(this,"Login bueno", Toast.LENGTH_SHORT).show()
}
#SuppressLint("ResourceAsColor")
override fun onError(message: String) {
binding.btnUsr
btn_usr.setBackgroundColor(R.color.colorFailLogin)
}
}
You can use Android databinding. I will provide a rough example.
You need to create ObservableFields that your views in XML can bind to.
For example
val isVisible : ObservableField<Boolean> = ObservableField();
Then create a binding adapter
#BindingAdapter("customVisibility")
fun setVisibility(view : View, visible : Boolean) {
view.visibility = if (visible) View.VISIBLE else View.INVISIBLE
}
Then bind in your XML
app:customVisibility="#{viewModel.isVisible}"
Then you can change the visibility of the views just by modifying the isVisible property of your view model.
I have wrote a primer in Android Databinding and Functional MVVM. You can check it for further details (written in Kotlin)
https://medium.com/tompee/android-data-binding-and-functional-mvvm-b311e4c98d
Related
MainActivity.kt Code
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
val db = Firebase.firestore
val firebaseFirestore : FirebaseFirestore = FirebaseFirestore.getInstance()
private var searchList: List<dataClass> = ArrayList()
private val adapter = adapter(searchList)
override fun onCreate(savedInstanceState: Bundle?) {
binding = ActivityMainBinding.inflate(layoutInflater)
super.onCreate(savedInstanceState)
setContentView(binding.root)
supportActionBar?.hide()
val search_list :RecyclerView = findViewById(R.id.search_list)
search_list.hasFixedSize()
search_list.layoutManager = LinearLayoutManager(this)
search_list.adapter = adapter
binding.searchField.addTextChangedListener(object : TextWatcher{
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
}
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
val search : String = binding.searchField.text.toString()
//Search in Firestore
searchInFirestore(search.toLowerCase())
}
override fun afterTextChanged(p0: Editable?) {
}
})
}
private fun searchInFirestore(search: String) {
firebaseFirestore.collection("mechanic").orderBy("city").startAt(search).endAt("$search\uf8ff").get()
.addOnCompleteListener {
if(it.isSuccessful){
searchList = it.result!!.toObjects(dataClass::class.java)
adapter.searchlist =searchList
adapter.notifyDataSetChanged()
}
else{
Log.d(TAG,"Error:${it.exception!!.message}")
}
}
}
}
Adapter.kt code
class adapter(var searchlist: List<dataClass> ) : RecyclerView.Adapter<adapter.viewholder>(){
inner class viewholder(itemView: View):RecyclerView.ViewHolder(itemView) {
val mname: TextView = itemView.findViewById(R.id.name)
val mphone : TextView = itemView.findViewById(R.id.phone)
val addres: TextView = itemView.findViewById(R.id.address)
fun bind(dataClass: dataClass){
mname.text = dataClass.name
mphone.text = dataClass.phone
addres.text = dataClass.address
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): viewholder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.single_search,parent,false)
return viewholder(view)
}
override fun onBindViewHolder(holder: viewholder, position: Int) {
holder.bind(searchlist[position])
}
override fun getItemCount(): Int {
return searchlist.size
}
}
the view in which i have to bind data
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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="match_parent"
android:layout_height="match_parent">
<TextView
android:id="#+id/name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="TextView"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.022" />
<TextView
android:id="#+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Phone no"
app:layout_constraintBaseline_toBaselineOf="#+id/phone"
app:layout_constraintStart_toStartOf="parent" />
<TextView
android:id="#+id/phone"
android:layout_width="144dp"
android:layout_height="19dp"
android:layout_marginTop="12dp"
android:layout_marginBottom="20dp"
android:text="TextView"
app:layout_constraintBottom_toTopOf="#+id/address"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.088"
app:layout_constraintStart_toEndOf="#+id/textView2"
app:layout_constraintTop_toBottomOf="#+id/name" />
<TextView
android:id="#+id/textView5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Address"
app:layout_constraintBaseline_toBaselineOf="#+id/address"
app:layout_constraintStart_toStartOf="parent" />
<TextView
android:id="#+id/address"
android:layout_width="260dp"
android:layout_height="65dp"
android:layout_marginTop="86dp"
android:text="TextView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.229"
app:layout_constraintStart_toEndOf="#+id/textView5"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Mainactivity.xml file
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<EditText
android:id="#+id/search_field"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ems="10"
android:inputType="textPersonName"
android:paddingTop="20sp"
android:text="Search here"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:layout_width="408dp"
android:layout_height="642dp"
android:id="#+id/search_list"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/search_field" />
</androidx.constraintlayout.widget.ConstraintLayout>
i want to implement a live search view in my app using firestore database but when i running the above code it doesn't showing any thing to me. I think there is a mistake in my adapter.kt and mainactivity.kt file. Please help me to correct it.
I am expecting to see the data while searching but it is not showing the data from firestore. I want it to be appear in search result.
dataclass.kt data class dataClass ( val name: String ="",val phone:String = "", val address : String ="" )
I think the issue is that you're setting content to binding and then not referencing the RecyclerLayout from that binding. Try using val search_list = binding.searchList instead of val search_list :RecyclerView = findViewById(R.id.search_list)
So I am trying to create a simple Note App, where the user gives data, gets stored in a database, and is displayed using a recycler view. Up until now, I can confirm that data is being stored in my database, and that data is also being retrieved from it and being initialized to a variable in the adapter class, but for some reason, the Adapter's OnCreateViewHolder(), OnBindViewHolder() and getItemCount() functions are not being called even after notifying the adapter of the change in data. This is my MainActivity and Adapter class.
class MainActivity : AppCompatActivity() {
private lateinit var viewModel: NoteViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val noteAdapter = NotesRVAdapter(this)
notesRV.adapter = noteAdapter
notesRV.layoutManager = LinearLayoutManager(this)
viewModel = ViewModelProvider(this,ViewModelProvider.AndroidViewModelFactory.getInstance(application)).get(NoteViewModel::class.java)
//access getAllNotes from NoteViewModel class from created instance
viewModel.getAllNotes.observe(
this,
Observer { list ->
list?.let {
Log.d("TAG","Observing")
NotesRVAdapter(this).updateList(it as ArrayList<NoteEntity>)
// Toast.makeText(this,"$it",Toast.LENGTH_LONG).show()
}
},
)
}
fun createNote(view: View) {
val intent = Intent(this,SecondActivity::class.java) // for creating a note
startActivity(intent)
}
}
Adapter Class
class NotesRVAdapter(private val context: Context) : RecyclerView.Adapter<NotesRVAdapter.NotesVH>() {
private var noteItem = ArrayList<NoteEntity>()
inner class NotesVH(noteView: View): RecyclerView.ViewHolder(noteView)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NotesVH {
val view = LayoutInflater.from(parent.context).inflate(R.layout.note_item,parent,false)
return NotesVH(view)
}
override fun onBindViewHolder(holder: NotesVH, position: Int) {
holder.itemView.Heading.text = noteItem[position].heading
holder.itemView.Description.text = noteItem[position].text
holder.itemView.Priority.text = noteItem[position].priority.toString()
}
override fun getItemCount(): Int {
return noteItem.size
}
fun updateList(list: ArrayList<NoteEntity>){
noteItem.clear()
noteItem.addAll(list)
// Toast.makeText(context,"${noteItem[0].text}",Toast.LENGTH_LONG).show()
notifyDataSetChanged()
}
}
The updateList function is getting called in mainactivity and the list is being passed in the noteitem and the toast also works, but that's it. All the override functions seem to be not working as I tried running a Log statement in them, but there was no entry for any Log in the Logcat.
I don't think there's a problem in the XML file as I could see the preview using the tools statement, but if someone needs to check it, here it is:
note_item.xml
<?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="wrap_content"
xmlns:tools="http://schemas.android.com/tools"
android:paddingHorizontal="30dp"
android:paddingVertical="30dp"
xmlns:app="http://schemas.android.com/apk/res-auto">
<TextView
android:id="#+id/Heading"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintRight_toLeftOf="#id/Priority"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text = "Note Heading"
android:textSize="35sp"
android:textStyle="bold"
android:textColor="#color/black"
/>
<TextView
android:id="#+id/Description"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="#id/Heading"
app:layout_constraintRight_toLeftOf="#id/Priority"
tools:text="Description"
android:textSize="25sp"
android:textColor="#color/black"
/>
<TextView
android:id="#+id/Priority"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="1"
android:textSize="60sp"
android:textColor="#color/black"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
main_activity.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/notesRV"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:layout_width="0dp"
android:layout_height="0dp"
tools:listitem="#layout/note_item"
/>
<ImageView
android:id="#+id/createNote"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:layout_marginBottom="80dp"
android:layout_marginRight="40dp"
android:layout_width="80dp"
android:layout_height="80dp"
android:src="#drawable/ic_baseline_add_24"
android:onClick="createNote"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
I would appreciate it if someone could help me with this issue. Thanks!
you are creating a new adapter instance here which is not attached to your recyclerview
NotesRVAdapter(this).updateList(it as ArrayList<NoteEntity>)
change it to the one attached to your recyclerview and that will be
noteAdapter.updateList(it as ArrayList<NoteEntity>)
In my app I'm using custom view containing a TextInputLayout with the following code:
<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat 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">
<com.google.android.material.textfield.TextInputLayout
android:id="#+id/input_layout"
style="#style/UbiInput2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="-"
android:orientation="horizontal"
app:boxBackgroundColor="#color/white">
<com.google.android.material.textfield.TextInputEditText
android:id="#+id/input_value"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:inputType="textPersonName" />
</com.google.android.material.textfield.TextInputLayout>
</androidx.appcompat.widget.LinearLayoutCompat>
And the kotlin
class TextInputView(context: Context, attrs: AttributeSet?) : LinearLayout(context, attrs) {
var errorMessage: String? = ""
init {
inflate(context, R.layout.text_input_view, this)
val attributes = context.obtainStyledAttributes(attrs, R.styleable.TextInputView)
val inputLayout = findViewById<TextInputLayout>(R.id.input_layout)
val inputValue = findViewById<TextInputEditText>(R.id.input_value)
inputLayout.hint = attributes.getString(R.styleable.TextInputView_hint)
inputValue.hint = attributes.getString(R.styleable.TextInputView_placeholderText)
inputLayout.isExpandedHintEnabled =
attributes.getBoolean(R.styleable.TextInputView_expandedHintEnabled, true)
errorMessage = attributes.getString(R.styleable.TextInputView_errorMessage)
inputLayout.isHelperTextEnabled = false
inputValue.inputType =
attributes.getInt(
R.styleable.TextInputView_android_inputType,
InputType.TYPE_CLASS_TEXT
)
if (attributes.hasValue(R.styleable.TextInputView_android_maxLength)) {
inputValue.filters += InputFilter.LengthFilter(
attributes.getInt(
R.styleable.TextInputView_android_maxLength,
100
)
)
}
inputValue.gravity =
attributes.getInt(R.styleable.TextInputView_android_gravity, Gravity.START)
if (attributes.getBoolean(R.styleable.TextInputView_android_gravity, false)) {
inputLayout.helperText = attributes.getString(R.styleable.TextInputView_helperText)
}
if (attributes.getBoolean(R.styleable.TextInputView_helperTextEnabled, false)) {
inputLayout.isHelperTextEnabled = true
inputLayout.helperText = attributes.getString(R.styleable.TextInputView_helperText)
}
inputLayout.startIconDrawable =
attributes.getDrawable(R.styleable.TextInputView_startIconDrawable)
attributes.recycle()
}
}
#BindingAdapter("textValue")
fun TextInputView.setTextValue(value: String?) {
value?.let {
setValue(value)
}
}
#InverseBindingAdapter(attribute = "textValue")
fun TextInputView.getTextValue(): String {
return value()
}
#BindingAdapter("textValueAttrChanged")
fun TextInputView.setListener(textAttrChanged: InverseBindingListener) {
val inputValue = findViewById<TextInputEditText>(R.id.input_value)
inputValue.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
}
override fun afterTextChanged(s: Editable?) {
textAttrChanged.onChange()
}
})
}
I have the following screen which go to the next fragment on button click. Using Navigation component.
But from the next screen, when I'm pressing back button to come back to the search form, the TextInputLayout values for hint, and helperText are all the same.
The only place I set those is inside the custom view. And from debugging I can see that all the correct values are set at that time.
I'm a bit out of ideas about what's going on there. Any hints would be appreciated.
Material library version used: 1.3.0-alpha04
All codebase has been migrated to view binding according the latest changes
The View is used as followed:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="viewModel"
type="com.ubigo.features.v2.products.taxi.SearchViewModel" />
<variable
name="dateFormat"
type="com.ubigo.ubicore.date.UbiDate" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/rental_search_root"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/background">
<androidx.cardview.widget.CardView
android:id="#+id/rental_search_form"
style="#style/WhiteCardView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/textView10">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/constraintLayout3"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.ubigo.ubicore.ui.TextInputView
android:id="#+id/pickup"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
app:hint="#string/product_start"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:startIconDrawable="#drawable/ic_calendar"
app:textValue="#{dateFormat.getPrettyDate(viewModel.pickupDateTime)}" />
<View
android:id="#+id/divider6"
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:background="?android:attr/listDivider"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/pickup" />
<com.ubigo.ubicore.ui.TextInputView
android:id="#+id/dropoff"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
app:hint="#string/product_end"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/divider6"
app:startIconDrawable="#drawable/ic_calendar"
app:textValue="#{dateFormat.getPrettyDate(viewModel.destinationDatetime)}" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
<com.ubigo.ubicore.ui.LoaderButton
android:id="#+id/rental_search_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="32dp"
android:layout_marginEnd="16dp"
android:text="#string/product_rental_show_car"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/rental_search_location" />
<androidx.cardview.widget.CardView
android:id="#+id/rental_search_location"
style="#style/WhiteCardView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/rental_search_form">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/constraintLayout4"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.ubigo.ubicore.ui.TextInputView
android:id="#+id/user_location"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
app:helperText="#string/product_rental_location"
app:helperTextEnabled="true"
app:hint="#string/product_pickup_location"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:startIconDrawable="#drawable/ic_location_on_black_24dp"
app:textValue="#{viewModel.depAddress}" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
<TextView
android:id="#+id/textView10"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="32dp"
android:text="#string/product_rental_weekend"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
And the initialization of the Fragment:
class RentalSearchFragment : Fragment() {
val viewModel: SearchViewModel by viewModel()
private val binding get() = _binding!!
private var _binding: FragmentRentalSearchBinding? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
if (_binding == null) {
_binding = FragmentRentalSearchBinding.inflate(inflater, container, false)
binding.lifecycleOwner = viewLifecycleOwner
binding.viewModel = viewModel
binding.dateFormat = UbiDate()
}
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
I was able to solve this exact problem by implementing onSaveInstanceState, onRestoreInstanceState, dispatchSaveInstanceState, dispatchRestoreInstanceState, and setValues as described here
http://www.devexchanges.info/2016/03/custom-compound-view-in-android.html
The linked above goes into all the necessary detail about how to implement these functions. There's also undoubtedly countless other sources you can find about how to implement this android paradigm.
I'm in the process of fiddling my way to a working app using Kotlin and I have hit a roadblock when trying to implement OnClickListeners for my three buttons. I have my RecyclerView populate properly, but despite following the recommendations on this SO post (except in Kotlin) and following the documentation, though I am still having trouble getting the implementation to work.
The code below is my adapter class for the implementation.
class BrowseHabitsAdapter(private val habits: ArrayList<Habit>) :
RecyclerView.Adapter<BrowseHabitsAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val itemView = LayoutInflater.from(parent.context).inflate(R.layout.habit_card, parent, false)
return ViewHolder(itemView, object: HabitClickListener {
override fun onDecrease(position: Int) {
val streak = itemView.dayCounter.text.toString().toInt()
itemView.dayCounter.text = streak.dec().toString()
}
override fun onIncrease(position: Int) {
val streak = itemView.dayCounter.text.toString().toInt()
itemView.dayCounter.text = streak.inc().toString()
}
override fun onEdit(position: Int) {
TODO("Change Activity to Edit")
}
})
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val currentItem = habits[position]
holder.habitTitle.text = currentItem.title
holder.streak.text = currentItem.streak.toString()
}
override fun getItemCount() = habits.size
class ViewHolder(itemView : View, listener : HabitClickListener) : RecyclerView.ViewHolder(itemView), View.OnClickListener {
val habitTitle: TextView = itemView.habitTitle
val streak: TextView = itemView.dayCounter
val decreaseCounterButton : Button = itemView.decreaseCounterButton
val increaseCounterButton : Button = itemView.increaseCounterButton
val listener = listener
init {
decreaseCounterButton.setOnClickListener(this)
increaseCounterButton.setOnClickListener(this)
}
override fun onClick(v: View?) {
when (itemView.id) {
itemView.decreaseCounterButton.id -> listener.onDecrease(this.layoutPosition)
itemView.increaseCounterButton.id -> listener.onIncrease(this.layoutPosition)
}
}
}
interface HabitClickListener {
fun onDecrease(position : Int)
fun onIncrease(position : Int)
fun onEdit(position : Int)
}
}
and the following is my XML code defining one of my cards:
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView 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:id="#+id/cardView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginTop="10dp"
android:layout_marginEnd="10dp"
app:cardBackgroundColor="#eeeeee"
app:cardCornerRadius="10dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:context=".MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:id="#+id/cardHeader"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<TextView
android:id="#+id/habitTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_marginTop="10dp"
android:layout_marginRight="10dp"
android:text="#string/default_card_title"
android:textSize="18sp" />
<Space
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_weight="1" />
<ImageView
android:id="#+id/settingsIcon"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_gravity="bottom"
android:layout_marginRight="10dp"
app:srcCompat="#android:drawable/ic_menu_manage" />
</LinearLayout>
<LinearLayout
android:id="#+id/cardControls"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="horizontal">
<Button
android:id="#+id/decreaseCounterButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:text="-"
android:textAllCaps="false"
android:textSize="30sp" />
<Space
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1" />
<TextView
android:id="#+id/dayCounter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:fontFamily="sans-serif-medium"
android:text="0"
android:textAlignment="center"
android:textSize="30sp"
android:textStyle="bold" />
<Space
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1" />
<Button
android:id="#+id/increaseCounterButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:text="+"
android:textSize="30sp" />
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
Any additional explanation that can be provided as to what I did wrong and what is going on in detail would be really appreciated!
You are in kotlin so need to implement View.OnClickListener you can directly use setOnClickListener on any view.
Inside your ViewHolder Class:
itemView.increaseCounterButton.setOnClickListener{
listener.onIncrease(this.layoutPosition)
}
itemView.decreaseCounterButton.setOnClickListener{
listener.onDecrease(this.layoutPosition)
}
It should be view?.id instead of itemView.id
override fun onClick(v: View?) {
when (v?.id) {
itemView.decreaseCounterButton.id -> listener.onDecrease(this.layoutPosition)
itemView.increaseCounterButton.id -> listener.onIncrease(this.layoutPosition)
}
}
Additionally, your code with bugs. You handle HabitClickListener only update UI, when you scroll your data will be update base on habits. It means it will be revert when you scroll. Make sure streak of model Habit is var
return ViewHolder(itemView, object: HabitClickListener {
override fun onDecrease(position: Int) {
habits[position].streak = habits[position].streak.dec()
itemView.dayCounter.text = shabits[position].streak.toString()
}
override fun onIncrease(position: Int) {
habits[position].streak = habits[position].streak.inc()
itemView.dayCounter.text = shabits[position].streak.toString()
}
override fun onEdit(position: Int) {
TODO("Change Activity to Edit")
}
})
Using LiveData<PagedList> to display a recycler view that hits a Room database. When I do a search on screen, the number of items returned is less, however, there seems to be a lot of extra white space between the recycler view and the other buttons. The recycler view itself shrinks, and then moves down on the screen.
Here is the way it looks before the data is refreshed (before I do a search):
Here is after:
Here is my adapter for the recyclerView:
class WorkPackagesRecyclerAdapter(
private val onWorkPackageClickListener: OnClickWorkPackage
) : PagedListAdapter<Workpackage,
WorkPackagesRecyclerAdapter.WorkPackagesViewHolder>(
REPO_COMPARATOR
) {
interface OnClickWorkPackage {
fun clickWorkPackage(workPackageId: String)
}
override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): WorkPackagesViewHolder {
val inflater = LayoutInflater.from(viewGroup.context)
val binding = inflate<ItemWorkPackageBinding>(
inflater,
R.layout.item_work_package,
viewGroup,
false
)
return WorkPackagesViewHolder(binding, viewGroup.context)
}
override fun onBindViewHolder(holder: WorkPackagesViewHolder, position: Int)
{
getItem(position)?.let {
holder.bind(it)
}
}
inner class WorkPackagesViewHolder(
internal var binding: ItemWorkPackageBinding,
internal val context: Context
) : RecyclerView.ViewHolder(binding.root), KoinComponent {
fun bind(data: Workpackage) {
val itemWorkPackagesViewModel: ItemWorkPackagesViewModel by inject{ parametersOf(data)}
this.binding.listItem.setOnClickListener {
onWorkPackageClickListener.clickWorkPackage(data.id)
}
this.binding.viewmodel = itemWorkPackagesViewModel
this.binding.executePendingBindings()
}
}
companion object {
private val REPO_COMPARATOR =
object : DiffUtil.ItemCallback<Workpackage>() {
override fun areItemsTheSame(
oldItem: Workpackage,
newItem: Workpackage
): Boolean =
oldItem.id == newItem.id
override fun areContentsTheSame(
oldItem: Workpackage,
newItem: Workpackage
): Boolean =
oldItem == newItem
}
}
Here is how I set up the PagedList:
val searchQuery: MutableLiveData<SearchAndSort> = MutableLiveData(
SearchAndSort("",
WorkpackagesRepository.Companion.SortedBy.WorkPackageNumber,
AscendDescend.ASC))
var workPackagesList = Transformations.switchMap(searchQuery) { searchQuery ->
val factory = workPackageStorageDao.searchWorkpackages(
searchQuery.searchText,
searchQuery.sortBy.type + " " + searchQuery.ascendDescend.text
)
val pagedListBuilder = LivePagedListBuilder<Int, Workpackage>(factory, pagingLimit)
pagedListBuilder.build()
}
Here is where I am observing the adapter:
workPackagesViewModel.workPackagesList.observe(this, Observer { wpList ->
wpList ?: return#Observer
adapter = WorkPackagesRecyclerAdapter(this)
adapter.submitList(wpList)
binding.workPackagesRecyclerView.adapter = adapter
adapter.notifyDataSetChanged()
})
Here is the layout of the recycler view
<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="viewModel"
type="com.bechtel.pf.ui.workpackages.WorkPackagesViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="727dp"
tools:layout_editor_absoluteY="1dp">
<EditText
android:id="#+id/search_bar_edit_text"
android:layout_width="match_parent"
android:layout_height="55dp"
android:layout_marginTop="#dimen/work_package_margin"
android:hint="#string/work_packages_search_hint"
app:layout_constraintBottom_toBottomOf="#id/guideline"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
<ImageButton
android:id="#+id/search_bar_magnifying_glass"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginTop="#dimen/work_package_margin"
android:background="#drawable/icons_search"
app:layout_constraintBottom_toBottomOf="#id/guideline"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
<androidx.constraintlayout.widget.Guideline
android:id="#+id/guideline"
android:layout_width="match_parent"
android:layout_height="0dp"
android:orientation="horizontal"
app:layout_constraintGuide_begin="#dimen/work_package_search_guideline_start"
/>
<com.google.android.material.button.MaterialButton
android:id="#+id/btnSortBy"
android:layout_width="0dp"
android:layout_height="45dp"
android:layout_marginTop="5dp"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"
android:backgroundTint="#color/colorPrimary"
app:layout_constraintEnd_toStartOf="#+id/btnAscendDescend"
app:layout_constraintHorizontal_weight="1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/guideline"
app:layout_constraintHorizontal_chainStyle="spread"
android:text="#string/work_packages_sort"
/>
<com.google.android.material.button.MaterialButton
android:id="#+id/btnAscendDescend"
android:layout_width="0dp"
android:layout_height="45dp"
android:layout_marginTop="5dp"
android:layout_marginEnd="10dp"
android:backgroundTint="#color/colorPrimary"
app:layout_constraintHorizontal_weight="1"
app:layout_constraintEnd_toStartOf="#+id/btnFilterBy"
app:layout_constraintStart_toEndOf="#+id/btnSortBy"
app:layout_constraintTop_toBottomOf="#+id/guideline"
app:layout_constraintHorizontal_chainStyle="spread"
android:text="#string/work_packages_ascend"
android:onClick="#{() -> viewModel.ascendingDescending()}"
/>
<com.google.android.material.button.MaterialButton
android:id="#+id/btnFilterBy"
android:layout_width="0dp"
android:layout_height="45dp"
android:layout_marginTop="5dp"
android:layout_marginEnd="10dp"
android:backgroundTint="#color/colorPrimary"
app:layout_constraintHorizontal_weight="1"
app:layout_constraintHorizontal_chainStyle="spread"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="#+id/btnAscendDescend"
app:layout_constraintTop_toBottomOf="#+id/guideline"
android:text="#string/work_packages_filter"/>
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/workPackagesRecyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="vertical"
android:layout_marginTop="75dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/btnFilterBy">
</androidx.recyclerview.widget.RecyclerView>
</androidx.constraintlayout.widget.ConstraintLayout>
Anyone ever seen anything like this before? I've also made sure that I am using layout_height = wrap_content in my recyclerview layout and item.xml files.
Screenshot inspector:
OK, it looks like I had set the recyclerview layout_constraintBottom_toBottomOf was set to 'parent' and since the total height of the recyclerview shrunk when the number of items shrunk, it was moving the view to the bottom of the screen. I also could get rid of the top margin attribute and the recycler view stayed below the buttons. So now I have:
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/workPackagesRecyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/btnFilterBy"/>
And it all looks good!