Kotlin doesnt get the data from the binding viewModel - kotlin

I'm trying to get the data that the user is enter in the autocompleteview but i doesnt receive it in the viewModel.
There's the xml file,
<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="connectionViewModel"
type="com.example.soccerinfo.connection.ConnectionViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<AutoCompleteTextView
android:id="#+id/mail"
style="#style/textStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="#string/enter_your_mail"
android:inputType="textEmailAddress"
android:text="#{connectionViewModel._connectionMailId}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.23000002" />
<Button
android:id="#+id/connectionButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/sign_in"
android:onClick="#{() -> connectionViewModel.onConnection()}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/mail" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
There is the viewModel, in the insertUpdate function I'm trying to display the connectionMailId. The connectionMailId display an empty string when I want to display ion the logs
class ConnectionViewModel(
val database: ConnectionDataBaseDao,
private val app : Application) : AndroidViewModel(app){
private val _eventConnectionMade = MutableLiveData<Boolean>()
val eventConnectionMade : LiveData<Boolean>
get() = _eventConnectionMade
val _mails : MutableLiveData<List<Connection>> = MutableLiveData()
var _connectionMailId = String()
init {
viewModelScope.launch(Dispatchers.IO) {
_mails.postValue(database.getAllConnections())
}
_connectionMailId = ""
}
fun insertUpdateConnection(mail: String){
viewModelScope.launch(Dispatchers.IO){
Timber.i("Email binding : $_connectionMailId")
var connection = Connection(mail)
if (requireNotNull(_mails.value?.any { connection -> connection.mailId.equals(mail) })){
update(connection)
}else{
insert(connection)
}
}
}
I hope you will help thanks a lot.

Try making _connectionMailId a live data of type String:
var _connectionMailId = MutableLiveData<String>()
also, use two way data binding:
android:text="#={connectionViewModel._connectionMailId}"

Related

AutoCompeleteTextView is not show ArrayList in dropDown kotlin using ViewBinding

AutoCompleteTextView is Replace with Spinner. AutoCompleteTextview inside TextInputLayout in Xml.
Data Fill using Custom ArrayAdapter in AutoCompeleteTextView but Data is not Show in AutoComplete TextView dropdowm.
MainActivity.kt
val numberList = ArrayList<Serve>()
numberList.add(Serve("101"))
numberList.add(Serve("101/2"))
numberList.add(Serve("201"))
numberList.add(Serve("202/3"))
numberList.add(Serve("205/1"))
val adapterSeveNo = CustomArrayAdapter(this,serveNo)
binding.autoCTVServeNo.setAdapter(adapterSeveNo)
binding.autoCTVServeNo.setOnItemClickListener { adapterView, view, i, l ->
val selectedItem = adapterView.getItemAtPosition(i).toString()
Toast.makeText(this,selectedItem,Toast.LENGTH_LONG).show()
}
activity.main.xml
<com.google.android.material.textfield.TextInputLayout
android:id="#+id/tilListNumber"
style="#style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
app:boxBackgroundMode="outline"
app:endIconMode="clear_text"
android:hint="Select Number.*"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/btnProductNext">
<AutoCompleteTextView
android:id="#+id/autoCTVServeNo"
android:layout_width="match_parent"
android:layout_height="60dp"
android:paddingStart="10dp"
android:paddingLeft="10dp"
android:inputType="none"/>
</com.google.android.material.textfield.TextInputLayout>
CustomArrayAdapter.kt
class CustomArrayAdapter( context: Context, val serveNo : ArrayList<Serve>) : ArrayAdapter<Serve>(context,0,serveNo) {
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
val binding = DropDownItemBinding.inflate(inflater)
val ser = serveNo.get(position)
binding.tvDropDown.text = ser.serveNo
return binding.root
}
}
drop_down_item.xml
<TextView
xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/tvDropDown"
android:padding="16dp"
android:maxLines="1"
android:ellipsize="end"
android:textAppearance="#style/TextAppearance.MaterialComponents.Subtitle1">
</TextView>
AutoCompleteTextview drop down is not data fill from ArrayList using ArrayAdapter in Viewbinding.

How to display a TextView when Recycler View is empty?

I would like to display a textView when my RecyclerView is empty.
I prepared this function but it doesn't work. I believe I should get the list from RecyclerView but I don't really know how.
I am in a fragment.
XML:
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recycler_view_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="visible"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:listitem="#layout/item_list" />
<TextView
android:id="#+id/tv_no_records"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="#string/nothing_to_display"
android:textSize="16sp"
android:visibility="gone" />
Fragment:
private fun displayList() {
val list = listOf<Shoe>()
if (list.isEmpty()) {
binding.recyclerViewList.visibility = View.VISIBLE
binding.tvNoRecords.visibility = View.GONE
} else {
binding.recyclerViewList.visibility = View.GONE
binding.tvNoRecords.visibility = View.VISIBLE
}
}
Adding the shoe (in ViewModel):
fun addShoe(shoe: Shoe) {
viewModelScope.launch(Dispatchers.IO) {
repository.addShoes(shoe)
}
}
Many thanks,
Anna
The conditions are inverted. When list is empty you are showing recyclerView and vice versa. Simply use a not check.
Consider below:
private fun displayList() {
val list = listOf<Shoe>()
if (list.isNotEmpty()) {
binding.recyclerViewList.visibility = View.VISIBLE
binding.tvNoRecords.visibility = View.GONE
} else {
binding.recyclerViewList.visibility = View.GONE
binding.tvNoRecords.visibility = View.VISIBLE
}
}

Retrofit2 returns a null response body with GET

I am using Retrofit2 to make a GET to github api but response.body() returns empty. I actually check the endpoints on Postman and it returns a body. I don't know what I am doing wrong that is causing this. I have included in the posts below my interface, viewmodel and my layout.
API interface
const val BASE_URL = "https://api.github.com/"
private val retrofit =
Retrofit.Builder().addConverterFactory(ScalarsConverterFactory.create()).baseUrl(
BASE_URL
).build()
object GithubUserApi {
val retrofitService: GithubApiService by lazy {
retrofit.create(GithubApiService::class.java)
}
}
interface GithubApiService {
#GET("search/users?sort=repositories&order=desc")
fun searchUsers(
#Query("q") query: String,
#Query("page") page: Int,
#Query("per_page") itemsPerPage: Int
): Call<String>
}
ViewModel
class OverviewViewModel : ViewModel() {
private val _response = MutableLiveData<String>()
val response: LiveData<String>
get() = _response
init {
getGithubUsers()
}
private fun getGithubUsers() {
GithubUserApi.retrofitService.searchUsers("location:LOCATION", 1, 50).enqueue(
object : Callback<String> {
override fun onFailure(call: Call<String>, t: Throwable) {
_response.value = "Failure: " + t.message
}
override fun onResponse(call: Call<String>, response: Response<String>) {
_response.value = response.body()
}
}
)
}
}
layout
<?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"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="overviewViewModel"
type="com.example.OverviewViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.overview.OverviewFragment">
<TextView
android:id="#+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{overviewViewModel.response}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
Allow Data Binding to Observe LiveData with the lifecycle of the fragment/activity.
binding.lifecycleOwner = this
That is what solved the problem for me... What an oversight!

Android Jetpack Safe Args Issue

I have an app which has one main activity and 3 fragments. Let's name them A, B and C as shown below:
Fragment A has a RecyclerView. When i click on an item in the
RecyclerView, it passes an Account object in safe args to the
Fragment B where it displays the data.
In Fragment B, the user can then press the edit button to edit the Account object, which then
navigates him/her to Fragment C.
In fragment C, the user can either press the back button to cancel
the modification and return to Fragment B, or the user can modify and
press save to go back to Fragment A.
The problem here is that, if after pressing the edit button, the user perform some modifications and then press the back button without saving, it still modifies the object temporarily (temporarily because if i close the app and then open it again, the object resets to its original state.).
Below is my code (this is a simple code just for the sake of reproducing the issue):
Fragment A.kt
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/Layout_Fragment_Account"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/RecyclerView_Account"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="#{ViewModel.loadingStatus==List.LIST ? View.VISIBLE : View.GONE}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="gone" />
...
</androidx.constraintlayout.widget.ConstraintLayout>
Fragment A.kt
private fun subscribeAccounts(accounts: List<Account>) {
val adapter = AccountAdapter(
/* The click listener to handle account on clicks */
AccountClickListener {
navigateTo(AccountFragmentDirections.actionFragmentAccountToFragmentViewAccount(it))
},
/* The click listener to handle popup menu for each accounts */
AccountOptionsClickListener { view, Account ->
//View Popup
}
)
binding.RecyclerViewAccount.apply {
/*
* State that layout size will not change for better performance
*/
setHasFixedSize(true)
/* Bind the layout manager */
layoutManager = LinearLayoutManager(requireContext())
/* Bind the adapter */
this.adapter = adapter
}
/* Submits the list for displaying */
adapter.submitList(accounts)
}
Fragment B.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="Account"
type="...Account" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/Layout_Credential_View"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="#+id/Account_Logo"
errorResource="#{#drawable/ic_account_placeholder}"
imageUrl="#{Account.logoUrl}"
loadingResource="#{#drawable/ic_image_loading}"
android:layout_width="100dp"
android:layout_height="100dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="#drawable/ic_account_placeholder" />
<TextView
android:id="#+id/Account_Name"
style="#style/Locky.Text.Title5.Name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="32dp"
android:text="#{Account.entryName}"
android:textAlignment="center"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/Account_Logo"
tools:text="This can be a very very very long title toooooo" />
...
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
Fragment B.kt
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
/* Binds the UI */
_binding = FragmentViewAccountBinding.inflate(inflater, container, false)
/* Instantiate the view model */
_viewModel = ViewModelProvider(this).get(ViewAccountViewModel::class.java)
/* Bind lifecycle owner to this */
binding.lifecycleOwner = this
/*
* Fetch the account object from argument
* Then bind account object to layout
*/
val account = ViewAccountFragmentArgs.fromBundle(requireArguments()).accountToVIEW
binding.account = account
_account = account
/* Returns the root view */
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.menu_credentials_actions, menu)
super.onCreateOptionsMenu(menu, inflater)
}
override fun onOptionsItemSelected(item: MenuItem) =
when (item.itemId) {
R.id.Action_Duplicate -> {
navigateTo(
ViewAccountFragmentDirections.actionFragmentViewAccountToFragmentAddAccount(
_account.apply {
this.id = 0
})
)
true
}
R.id.Action_Edit -> {
navigateTo(
ViewAccountFragmentDirections.actionFragmentViewAccountToFragmentAddAccount(
_account
)
)
true
}
else -> false
}
}
Fragment C.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>
<import type="...Constants" />
<variable
name="ViewModel"
type="...AddAccountViewModel" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingBottom="16dp">
...
<!--
**************** Require Text Fields ****************
-->
<com.google.android.material.textfield.TextInputLayout
android:id="#+id/Account_Name"
style="#style/Locky.TextBox.Default"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:hint="#string/field_account_name"
app:endIconMode="clear_text"
app:helperText="#string/label_required"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/Barrier_Logo"
app:startIconDrawable="#drawable/ic_profile">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textCapWords"
android:text="#={ViewModel.entryName}" />
</com.google.android.material.textfield.TextInputLayout>
...
</LinearLayout>
</layout>
Fragment C.kt
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentAddAccountBinding.inflate(inflater, container, false)
/* Binds the UI */
_binding = FragmentAddAccountBinding.inflate(inflater, container, false)
/* Instantiate the view model */
_viewModel = ViewModelProvider(this).get(AddAccountViewModel::class.java)
/* Bind view model to layout */
binding.viewModel = viewModel
/* Bind lifecycle owner to this */
binding.lifecycleOwner = this
/*
* Fetch the account object from argument
* And set it to view model for two way binding
*/
val account = AddAccountFragmentArgs.fromBundle(requireArguments()).accountToADD
viewModel.setAccount(account)
/* Returns the root view */
return binding.root
}
Fragment C view model
class AddAccountViewModel(application: Application) : ObservableViewModel(application) {
/**
* Bindable two-way binding
**/
private lateinit var _account: Account
var entryName: String
#Bindable get() {
return _account.entryName
}
set(value) {
_account.entryName = value
notifyPropertyChanged(BR.entryName)
}
var logoUrl: String?
#Bindable get() {
return _account.logoUrl
}
set(value) {
_account.logoUrl = value ?: ""
notifyPropertyChanged(BR.logoUrl)
}
internal fun setAccount(account: Account?) {
this._account = account ?: Account()
}
}
navigation.xml
<fragment
android:id="#+id/Fragment_A"
android:name="...AccountFragment"
android:label="Accounts"
tools:layout="#layout/fragment_account">
<action
android:id="#+id/action_Fragment_Account_to_BottomSheet_Fragment_Account_Filter"
app:destination="#id/BottomSheet_Fragment_Account_Filter" />
<action
android:id="#+id/action_Fragment_Account_to_Fragment_View_Account"
app:destination="#id/Fragment_View_Account" />
</fragment>
<fragment
android:id="#+id/Fragment_B"
android:name="...ViewAccountFragment"
android:label="View Account"
tools:layout="#layout/fragment_view_account">
<action
android:id="#+id/action_Fragment_View_Account_to_Fragment_Add_Account"
app:destination="#id/Fragment_Add_Account" />
<argument
android:name="ACCOUNT_toVIEW"
app:argType="....Account" />
</fragment>
<fragment
android:id="#+id/Fragment_C"
android:name="...AddAccountFragment"
android:label="Add Account"
tools:layout="#layout/fragment_add_account">
<action
android:id="#+id/action_Fragment_Add_Account_to_BottomSheet_Fragment_Account_Logo"
app:destination="#id/BottomSheet_Fragment_Account_Logo" />
<argument
android:name="ACCOUNT_ToADD"
app:argType="...Account" />
</fragment>
Below is a demonstration of the issue:
I've tried a quick hack where i save an instance of an unmodified account object and before the user leaves the fragment i reset the changes by assigning each variable but this is not efficient. I think i am doing something wrong here with the safe args?
Can someone please help me. I really can't figure this one out. Thank you

Root casue for NoSuchElementException: Collection is empty

I am facing Crash in Firebase Crashlytics on bind method but unable to find the root cause. I have added a Null check but still, the issue not fixed.
Firebase Crash Report
Fatal Exception: java.util.NoSuchElementException: Collection is empty.
at kotlin.collections.CollectionsKt___CollectionsKt.first(_Collections.kt:184)
at com.transferhome.contacts.ContactsAdapter.bind(ContactsAdapter.kt:25)
at com.transferhome.contacts.ContactsBaseAdapter.onBindViewHolder(ContactsBaseAdapter.kt:38)
at com.transferhome.contacts.ContactsBaseAdapter.onBindViewHolder(ContactsBaseAdapter.kt:19)
at androidx.recyclerview.widget.RecyclerView$Adapter.onBindViewHolder(RecyclerView.java:6781)
at androidx.recyclerview.widget.RecyclerView$Adapter.bindViewHolder(RecyclerView.java:6823)
at androidx.recyclerview.widget.RecyclerView$Recycler.tryBindViewHolderByDeadline(RecyclerView.java:5752)
at androidx.recyclerview.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:6019)
at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:5858)
at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:5854)
at androidx.recyclerview.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:2230)
at androidx.recyclerview.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1557)
at androidx.recyclerview.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1517)
at androidx.recyclerview.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:612)
at androidx.recyclerview.widget.RecyclerView.dispatchLayoutStep2(RecyclerView.java:3924)
at androidx.recyclerview.widget.RecyclerView.onMeasure(RecyclerView.java:3336)
at android.view.View.measure(View.java:22216)
at androidx.constraintlayout.widget.ConstraintLayout.internalMeasureChildren(ConstraintLayout.java:1227)
at androidx.constraintlayout.widget.ConstraintLayout.onMeasure(ConstraintLayout.java:1572)
at android.view.View.measure(View.java:22216)
at androidx.viewpager.widget.ViewPager.onMeasure(ViewPager.java:1638)
at android.view.View.measure(View.java:22216)
at androidx.constraintlayout.widget.ConstraintLayout.onMeasure(ConstraintLayout.java:1676)
at android.view.View.measure(View.java:22216)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6671)
at androidx.coordinatorlayout.widget.CoordinatorLayout.onMeasureChild(CoordinatorLayout.java:733)
at com.google.android.material.appbar.HeaderScrollingViewBehavior.onMeasureChild(HeaderScrollingViewBehavior.java:95)
at com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior.onMeasureChild(AppBarLayout.java:1556)
at androidx.coordinatorlayout.widget.CoordinatorLayout.onMeasure(CoordinatorLayout.java:803)
at android.view.View.measure(View.java:22216)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6671)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:185)
at androidx.appcompat.widget.ContentFrameLayout.onMeasure(ContentFrameLayout.java:143)
at android.view.View.measure(View.java:22216)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6671)
at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1539)
at android.widget.LinearLayout.measureVertical(LinearLayout.java:823)
at android.widget.LinearLayout.onMeasure(LinearLayout.java:702)
at android.view.View.measure(View.java:22216)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6671)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:185)
at android.view.View.measure(View.java:22216)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6671)
at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1539)
at android.widget.LinearLayout.measureVertical(LinearLayout.java:823)
at android.widget.LinearLayout.onMeasure(LinearLayout.java:702)
at android.view.View.measure(View.java:22216)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6671)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:185)
at com.android.internal.policy.DecorView.onMeasure(DecorView.java:831)
at android.view.View.measure(View.java:22216)
at android.view.ViewRootImpl.performMeasure(ViewRootImpl.java:2589)
at android.view.ViewRootImpl.measureHierarchy(ViewRootImpl.java:1631)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1885)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1515)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:7266)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:981)
at android.view.Choreographer.doCallbacks(Choreographer.java:790)
at android.view.Choreographer.doFrame(Choreographer.java:721)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:967)
at android.os.Handler.handleCallback(Handler.java:808)
at android.os.Handler.dispatchMessage(Handler.java:101)
at android.os.Looper.loop(Looper.java:166)
at android.app.ActivityThread.main(ActivityThread.java:7529)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:245)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:921)
ContactsAdapter.kt
class ContactsAdapter ( private val onItemSelect: (contact: Contact) -> Unit )
: ContactsBaseAdapter<ItemContactFavListBinding>() {
var contactFavSignal = MutableLiveData<Contact>().apply { }
override fun bind(contact: Contact, holder: ContactVH<ItemContactFavListBinding>) {
val binding = holder.binding
binding.contactName.text = contact.name
binding.contactNumber.text = contact.numbers.first().phone
binding.imgContactHeart.setOnClickListener(View.OnClickListener {
val backgroundImageName = (binding.imgContactHeart.getTag()).toString()
if(backgroundImageName.equals("2")) {
binding.imgContactHeart.setImageResource(R.drawable.heart_selected_yellow)
EventBus.getDefault().post(CustomEvent(contact,true));
binding.imgContactHeart.setTag("1")
binding.imgContactHeart.setImageResource(R.drawable.heart_selected_yellow)
}else if(backgroundImageName.equals("1")) {
binding.imgContactHeart.setImageResource(R.drawable.heart_selected_yellow)
EventBus.getDefault().post(CustomEvent(contact,false));
binding.imgContactHeart.setTag("2")
binding.imgContactHeart.setImageResource(R.drawable.heart)
}
})
var num = contact.numbers.first().phone
if(num.contains(" "))
num = num.replace(" ","")
if(num.startsWith("0"))
num = num.removePrefix("0")
if(num.startsWith("+"))
num = num.removePrefix("+")
var isFav:Boolean=false
for (x in 0..FavoritesFragment.listFavNumbers.size-1)
{
var num2 = FavoritesFragment.listFavNumbers[x]
if(num2.endsWith(num))
isFav=true
}
if(isFav)
{
binding.imgContactHeart.setTag("1")
binding.imgContactHeart.setImageResource(R.drawable.heart_selected_yellow)
}
else
{
binding.imgContactHeart.setTag("2")
binding.imgContactHeart.setImageResource(R.drawable.heart)
}
showContactProfileImage(contact, binding.contactImage)
holder.binding.root.setOnClickListener {
onItemSelect.invoke(contact)
}
}
override fun provideBinding(inflater: LayoutInflater, parent: ViewGroup): ItemContactFavListBinding {
return ItemContactFavListBinding.inflate(inflater, parent, false)
}
}
ContactBaseAdater.kt
abstract class ContactsBaseAdapter<T : ViewDataBinding> :
PagedListAdapter<Contact, ContactsBaseAdapter.ContactVH<T>>(object : DiffUtil.ItemCallback<Contact>() {
override fun areItemsTheSame(oldItem: Contact, newItem: Contact): Boolean {
return oldItem.name == newItem.name || oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: Contact, newItem: Contact): Boolean {
return oldItem.name == newItem.name
}
} ) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ContactVH<T> {
return ContactVH(provideBinding(LayoutInflater.from(parent.context), parent))
}
override fun onBindViewHolder(holder: ContactVH<T>, position: Int) {
val contact = getItem(position)
if (contact != null) {
bind(contact, holder)
}
}
abstract fun bind(contact: Contact, holder: ContactVH<T>)
abstract fun provideBinding(inflater: LayoutInflater, parent: ViewGroup): T
class ContactVH<T : ViewDataBinding>(val binding: T) : RecyclerView.ViewHolder(binding.root)
fun showContactProfileImage(contact: Contact, imageHolder: ImageView?) {
UiUtils.showContactProfileImage(contact, imageHolder)
}
}
item_contact_fav_list.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<RelativeLayout android:layout_width="match_parent"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:layout_height="wrap_content"
android:orientation="horizontal"
tools:ignore="UseCompoundDrawables">
<ImageView
android:id="#+id/contact_image"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:layout_alignParentLeft="true"
android:contentDescription="#null"/>
<LinearLayout android:layout_width="match_parent"
android:layout_height="50dp"
android:orientation="vertical"
android:weightSum="2"
android:layout_toLeftOf="#+id/img_contact_heart"
android:layout_toRightOf="#+id/contact_image"
android:layout_marginLeft="5dp">
<TextView
android:id="#+id/contact_name"
android:textSize="16sp"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_gravity="center"
android:layout_weight="1"
android:singleLine="true"
android:ellipsize="middle"
android:focusable="false"
android:textColor="#color/colorBlack"
android:layout_marginTop="3dp"
tools:text="Contact Name"/>
<TextView
android:id="#+id/contact_number"
android:textSize="15sp"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_gravity="center"
android:layout_weight="1"
android:singleLine="true"
android:textColor="#android:color/darker_gray"
tools:text="Contact Name"
android:layout_marginLeft="5dp"
/>
</LinearLayout>
<ImageView
android:layout_alignParentRight="true"
android:id="#+id/img_contact_heart"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/heart"
android:layout_centerVertical="true"
/>
</RelativeLayout>
</layout>
Let's take a look at the stacktrace:
Fatal Exception: java.util.NoSuchElementException: Collection is empty.
at kotlin.collections.CollectionsKt___CollectionsKt.first(_Collections.kt:184)
at com.transferhome.contacts.ContactsAdapter.bind(ContactsAdapter.kt:25)
Most probably, the line referred to is this one:
binding.contactNumber.text = contact.numbers.first().phone
the call to first() in particular. If contact.numbers is empty, first() will throw a NoSuchElementException. Your code does not handle this case.
The easiest solution would be to set the text to null if there are no numbers:
binding.contactNumber.text = contact.numbers.firstOrNull()?.phone
Why would you add a nullcheck if the exception was not a NullPointerException?