This is my first project using Kotlin, so I'm practice to create an app and using architecture components Room Database, Android View Model and LiveData, but I got this error message.
And the mobile crash unexpectedly,
I don't know how to solve it.
Thanks.
The build.gradle
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: "kotlin-kapt"
android {
compileSdkVersion 29
buildToolsVersion "29.0.2"
defaultConfig {
applicationId "com.leaf76.architectureexample"
minSdkVersion 21
targetSdkVersion 29
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
def lifecycle_version = "2.2.0"
def room_version = "2.2.5"
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.1.0'
// com.android.support.cardview => androidx
implementation "androidx.cardview:cardview:1.0.0"
// com.android.support.design => androidx
implementation "com.google.android.material:material:1.0.0"
implementation 'androidx.core:core-ktx:1.2.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
// LiveData
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
// Annotation processor
kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"
// Room
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
implementation "androidx.room:room-ktx:$room_version"
}
Error message
2020-03-27 02:50:27.987 12708-12708/com.leaf76.architectureexample E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.leaf76.architectureexample, PID: 12708
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.leaf76.architectureexample/com.leaf76.architectureexample.MainActivity}: java.lang.RuntimeException: Cannot create an instance of class com.leaf76.architectureexample.NoteViewModel
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3448)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3595)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:83)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2147)
at android.os.Handler.dispatchMessage(Handler.java:107)
at android.os.Looper.loop(Looper.java:237)
at android.app.ActivityThread.main(ActivityThread.java:7811)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1076)
Caused by: java.lang.RuntimeException: Cannot create an instance of class com.leaf76.architectureexample.NoteViewModel
at androidx.lifecycle.ViewModelProvider$NewInstanceFactory.create(ViewModelProvider.java:221)
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:187)
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:150)
at com.leaf76.architectureexample.MainActivity.onCreate(MainActivity.kt:21)
at android.app.Activity.performCreate(Activity.java:7955)
at android.app.Activity.performCreate(Activity.java:7944)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1307)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3423)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3595)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:83)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2147)
at android.os.Handler.dispatchMessage(Handler.java:107)
at android.os.Looper.loop(Looper.java:237)
at android.app.ActivityThread.main(ActivityThread.java:7811)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1076)
Caused by: java.lang.InstantiationException: java.lang.Class<com.leaf76.architectureexample.NoteViewModel> has no zero argument constructor
at java.lang.Class.newInstance(Native Method)
at androidx.lifecycle.ViewModelProvider$NewInstanceFactory.create(ViewModelProvider.java:219)
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:187)
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:150)
at com.leaf76.architectureexample.MainActivity.onCreate(MainActivity.kt:21)
at android.app.Activity.performCreate(Activity.java:7955)
at android.app.Activity.performCreate(Activity.java:7944)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1307)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3423)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3595)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:83)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2147)
at android.os.Handler.dispatchMessage(Handler.java:107)
at android.os.Looper.loop(Looper.java:237)
at android.app.ActivityThread.main(ActivityThread.java:7811)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1076)
Note.kt
package com.leaf76.architectureexample
import androidx.room.Entity
import androidx.room.PrimaryKey
#Entity(tableName = "note_table")
data class Note(var title: String, var description: String, var priorty: Int) {
#PrimaryKey(autoGenerate = true)
var Id: Int = 0
}
NoteDatabase
package com.leaf76.architectureexample
import android.content.Context
import android.os.AsyncTask
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.sqlite.db.SupportSQLiteDatabase
#Database(entities = [Note::class], version = 1, exportSchema = false)
abstract class NoteDatabase : RoomDatabase() {
abstract fun noteDao(): NoteDao
// static parameters
companion object {
private lateinit var instance: NoteDatabase
fun getInstance(context: Context): NoteDatabase {
if (instance == null) {
synchronized(NoteDatabase::class.java) {
instance = Room.databaseBuilder(
context.applicationContext,
NoteDatabase::class.java, "note_database"
)
.fallbackToDestructiveMigration()
.addCallback(roomCallbackL)
.build()
}
}
return instance
}
private val roomCallbackL: Callback = object : Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
PopulateDbAsyncTask(instance).execute()
}
}
private class PopulateDbAsyncTask(db: NoteDatabase) : AsyncTask<Void, Void, Void>() {
private val noteDao: NoteDao = db.noteDao()
override fun doInBackground(vararg voids: Void): Void? {
noteDao.insert(Note("Title1", "Description1", 1))
noteDao.insert(Note("Title2", "Description2", 2))
noteDao.insert(Note("Title3", "Description3", 3))
return null
}
}
}
}
NoteRepository
package com.leaf76.architectureexample
import android.app.Application
import android.os.AsyncTask
import androidx.lifecycle.LiveData
class NoteRepository(application: Application) {
// The lateinit avoid Nullable
private var noteDao: NoteDao
private var allNotes: LiveData<List<Note>>
init {
val database = NoteDatabase.getInstance(application.applicationContext)
noteDao = database.noteDao()
allNotes = noteDao.getAllNotes()
}
fun insert(note: Note) {
InsertNoteAsyncTask(noteDao).execute(note)
}
fun update(note: Note) {
UpdateNoteAsyncTask(noteDao).execute(note)
}
fun delete(note: Note) {
DeleteNoteAsyncTask(noteDao).execute(note)
}
fun deleteAllnotes() {
DeleteAllNotesAsyncTask(noteDao).execute()
}
fun getAllNotes(): LiveData<List<Note>> {
return allNotes
}
companion object {
// Insert note
private class InsertNoteAsyncTask(var noteDao: NoteDao) :
AsyncTask<Note, Void, Void>() {
override fun doInBackground(vararg notes: Note): Void? {
noteDao.insert(notes[0])
return null
}
}
// Update note
private class UpdateNoteAsyncTask(var noteDao: NoteDao) :
AsyncTask<Note, Void, Void>() {
override fun doInBackground(vararg notes: Note): Void? {
noteDao.update(notes[0])
return null
}
}
// Delete note
private class DeleteNoteAsyncTask(var noteDao: NoteDao) :
AsyncTask<Note, Void, Void>() {
override fun doInBackground(vararg notes: Note): Void? {
noteDao.delete(notes[0])
return null
}
}
// Delete all note
private class DeleteAllNotesAsyncTask(var noteDao: NoteDao) :
AsyncTask<Void, Void, Void>() {
override fun doInBackground(vararg voids: Void): Void? {
noteDao.deleteAllNotes()
return null
}
}
}
}
NoteViewModel
package com.leaf76.architectureexample
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
class NoteViewModel(application: Application) : AndroidViewModel(application) {
private var repository: NoteRepository = NoteRepository(application)
private var allNotes: LiveData<List<Note>> = repository.getAllNotes()
fun insert(note: Note){
repository.insert(note)
}
fun update(note:Note){
repository.update(note)
}
fun delete(note: Note){
repository.delete(note)
}
fun deleteAllNotes(){
repository.deleteAllnotes()
}
fun getAllNotes(): LiveData<List<Note>>{
return allNotes
}
}
MainActivity
package com.leaf76.architectureexample
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
class MainActivity : AppCompatActivity() {
private val TAG: String = "MainActivity"
private lateinit var noteViewModel: NoteViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Log.i(TAG,"MainActivity entry on create")
noteViewModel = ViewModelProvider(this).get(NoteViewModel::class.java)
Log.i(TAG,"Get note view model")
noteViewModel.getAllNotes().observe(this, Observer<List<Note>> {
Toast.makeText(this,"onChanged",Toast.LENGTH_LONG).show()
})
Log.i(TAG, "Get Toast")
}
}
It works!!!
When I remove this instance==null condition, but I still don't know kotlin how to run it on Singleton pattern.
// static parameters
companion object {
private lateinit var instance: NoteDatabase
fun getInstance(context: Context): NoteDatabase {
if (instance == null) {
synchronized(NoteDatabase::class.java) {
instance = Room.databaseBuilder(
context.applicationContext,
NoteDatabase::class.java, "note_database"
)
.fallbackToDestructiveMigration()
.addCallback(roomCallbackL)
.build()
}
}
return instance
}
to
// static parameters
companion object {
private lateinit var instance: NoteDatabase
fun getInstance(context: Context): NoteDatabase {
synchronized(NoteDatabase::class.java) {
instance = Room.databaseBuilder(
context.applicationContext,
NoteDatabase::class.java, "note_database"
)
.fallbackToDestructiveMigration()
.addCallback(roomCallbackL)
.build()
}
return instance
}
Related
I am simply trying to setup the ClickListener for changing the user name. Google's tutorials emphasize fragments, which for the moment feels like overkill.
The app crashes when I navigate to the ManageUsers activity. Based on what I've seen in other examples and the Android documentation, I thought I had the View Binding set up properly.
UserListAdapter.kt
package com.neillbarrett.debitsandcredits
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.EditText
import android.widget.TextView
import android.widget.Toast
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.neillbarrett.debitsandcredits.database.UsersTable
import com.neillbarrett.debitsandcredits.databinding.ActivityManageUsersBinding
class UserListAdapter(private val userSelect: (UsersTable?) -> Unit) :
ListAdapter<UsersTable, UserListAdapter.UserViewHolder>(UsersComparator()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserListAdapter.UserViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.activity_manage_users, parent, false)
return UserViewHolder.create(parent)
}
override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
val current = getItem(position)
holder.bind(current, userSelect)
}
class UserViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val userView: TextView = itemView.findViewById(R.id.tv_UserName)
// var text: String? = null
fun bind(usersTable: UsersTable?, userSelect: (UsersTable?) -> Unit, text: String = usersTable?.userName.toString()) {
userView.text = text
itemView.setOnClickListener { View.OnClickListener {
/* if (View.) { }*/
val nameSelected = userSelect(usersTable)
//userSelect(usersTable)
//need to assign the result of the clicklistener to the editText
//binding.etEditName.setText(R.layout.activity_list_of_users.toString())
}}
}
companion object {
fun create(parent: ViewGroup) : UserViewHolder {
val view: View = LayoutInflater.from(parent.context)
.inflate(R.layout.activity_manage_users, parent, false)
return UserViewHolder(view)
}
}
}
class UsersComparator : DiffUtil.ItemCallback<UsersTable>() {
override fun areItemsTheSame(oldItem: UsersTable, newItem: UsersTable): Boolean {
return oldItem.userName == newItem.userName
}
override fun areContentsTheSame(oldItem: UsersTable, newItem: UsersTable): Boolean {
return oldItem == newItem
}
}
}
ManageUsers.kt
package com.neillbarrett.debitsandcredits
import android.content.Context
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.text.TextUtils
import android.widget.Button
import android.widget.EditText
import android.widget.Toast
import androidx.activity.viewModels
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.neillbarrett.debitsandcredits.database.CreditsAndDebitsApp
import com.neillbarrett.debitsandcredits.database.UsersTable
import com.neillbarrett.debitsandcredits.databinding.ActivityManageUsersBinding
class ManageUsers : AppCompatActivity() {
lateinit var binding: ActivityManageUsersBinding
lateinit var recyclerView: RecyclerView
lateinit var editTextAddUser: EditText
lateinit var editTextChangeUser: EditText
lateinit var newUser: String
var userSelect: ((UsersTable?) -> Unit) = {}
var position: Long = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityManageUsersBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
//setContentView(R.layout.activity_manage_users)
val userViewModel: UserViewModel by viewModels {
UserViewModelFactory((application as CreditsAndDebitsApp).repository)
}
recyclerView = findViewById(R.id.rec_view_userList)
editTextAddUser = findViewById(R.id.et_AddUser)
editTextChangeUser = findViewById(R.id.et_Edit_Name)
val adapter = UserListAdapter(userSelect)
binding.recViewUserList.adapter = adapter
binding.recViewUserList.layoutManager = LinearLayoutManager(this)
//recyclerView.adapter = adapter
//recyclerView.layoutManager = LinearLayoutManager(this)
userViewModel.allUsers.observe(this, Observer() {user ->
user?.let { adapter.submitList(it) }
})
val btnAddUser = findViewById<Button>(R.id.btn_AddUser)
binding.btnAddUser.setOnClickListener {
// btnAddUser.setOnClickListener {
if (TextUtils.isEmpty(editTextAddUser.text)) {
Toast.makeText(this, "User name cannot be empty", Toast.LENGTH_SHORT).show()
} else {
newUser = editTextAddUser.text.toString()
// Log.i("Add user button", "Username put into newUser")
userViewModel.insertUser(UsersTable(0, newUser))
// Toast.makeText(this, "Username added to table", Toast.LENGTH_SHORT).show()
// Log.i("Add user button", "Username added to table")
}
}
val btnChangeUser = findViewById<Button>(R.id.btn_ChangeUserName)
binding.btnChangeUserName.setOnClickListener {
// btnChangeUser.setOnClickListener {
Toast.makeText(this, "Selected position is ${recyclerView.getChildAdapterPosition(it)}", Toast.LENGTH_SHORT).show()
/* if (recyclerView.getChildAdapterPosition(it) == -1) {
Toast.makeText(this, "Select a name.", Toast.LENGTH_SHORT).show()
} else {
if (editTextChangeUser.text.toString() == recyclerView.adapter.toString()) {
Toast.makeText(this, "Name has not been changed.", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(this, "Name would have been changed.", Toast.LENGTH_SHORT).show()
val rvItemRecId: Long
rvItemRecId = adapter.getItemId(position.toInt())
userViewModel.updateUser(UsersTable(rvItemRecId.toInt(), adapter.toString()))
}
}*/
}
}
}
UserViewModel.kt
package com.neillbarrett.debitsandcredits
import androidx.lifecycle.*
import com.neillbarrett.debitsandcredits.database.UsersTable
import kotlinx.coroutines.launch
import java.lang.IllegalArgumentException
class UserViewModel(private val repository: UserRepository) : ViewModel() {
// Using LiveData and caching what allWords returns has several benefits:
// - We can put an observer on the data (instead of polling for changes) and only update the
// the UI when the data actually changes.
// - Repository is completely separated from the UI through the ViewModel.
val allUsers: LiveData<List<UsersTable>> = repository.allUsers.asLiveData()
/**
* Launching a new coroutine to insert the data in a non-blocking way
*/
fun insertUser(user: UsersTable) = viewModelScope.launch {
repository.insertUser(user)
//repository.insertUser(usersTable = List<UsersTable>())
//repository.insertUser(UsersTable(0, userName = user.userName))
}
fun updateUser(user: UsersTable) = viewModelScope.launch {
repository.updateUser(user)
}
fun deleteUser(user: UsersTable) = viewModelScope.launch {
repository.deleteUser(user)
}
}
class UserViewModelFactory(private val repository: UserRepository) : ViewModelProvider.Factory{
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(UserViewModel::class.java)) {
#Suppress("UNCHECKED_CAST")
return UserViewModel(repository) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
The LogCat shows this:
2022-11-25 11:43:59.427 8217-8217/com.neillbarrett.debitsandcredits E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.neillbarrett.debitsandcredits, PID: 8217
java.lang.NullPointerException: itemView.findViewById(R.id.tv_UserName) must not be null
at com.neillbarrett.debitsandcredits.UserListAdapter$UserViewHolder.<init>(UserListAdapter.kt:30)
at com.neillbarrett.debitsandcredits.UserListAdapter$UserViewHolder$Companion.create(UserListAdapter.kt:51)
at com.neillbarrett.debitsandcredits.UserListAdapter.onCreateViewHolder(UserListAdapter.kt:21)
at com.neillbarrett.debitsandcredits.UserListAdapter.onCreateViewHolder(UserListAdapter.kt:15)
at androidx.recyclerview.widget.RecyclerView$Adapter.createViewHolder(RecyclerView.java:7078)
at androidx.recyclerview.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:6235)
at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:6118)
at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:6114)
at androidx.recyclerview.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:2303)
at androidx.recyclerview.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1627)
at androidx.recyclerview.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1587)
at androidx.recyclerview.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:665)
at androidx.recyclerview.widget.RecyclerView.dispatchLayoutStep2(RecyclerView.java:4134)
at androidx.recyclerview.widget.RecyclerView.onMeasure(RecyclerView.java:3540)
at android.view.View.measure(View.java:26411)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:7845)
at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1552)
at android.widget.LinearLayout.measureVertical(LinearLayout.java:842)
at android.widget.LinearLayout.onMeasure(LinearLayout.java:721)
at android.view.View.measure(View.java:26411)
at androidx.constraintlayout.widget.ConstraintLayout$Measurer.measure(ConstraintLayout.java:811)
at androidx.constraintlayout.core.widgets.analyzer.BasicMeasure.measure(BasicMeasure.java:466)
at androidx.constraintlayout.core.widgets.analyzer.BasicMeasure.measureChildren(BasicMeasure.java:134)
at androidx.constraintlayout.core.widgets.analyzer.BasicMeasure.solverMeasure(BasicMeasure.java:278)
at androidx.constraintlayout.core.widgets.ConstraintWidgetContainer.measure(ConstraintWidgetContainer.java:120)
at androidx.constraintlayout.widget.ConstraintLayout.resolveSystem(ConstraintLayout.java:1594)
at androidx.constraintlayout.widget.ConstraintLayout.onMeasure(ConstraintLayout.java:1708)
at android.view.View.measure(View.java:26411)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:7845)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
at androidx.appcompat.widget.ContentFrameLayout.onMeasure(ContentFrameLayout.java:145)
at android.view.View.measure(View.java:26411)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:7845)
at androidx.appcompat.widget.ActionBarOverlayLayout.onMeasure(ActionBarOverlayLayout.java:496)
at android.view.View.measure(View.java:26411)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:7845)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
at android.view.View.measure(View.java:26411)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:7845)
at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1552)
at android.widget.LinearLayout.measureVertical(LinearLayout.java:842)
at android.widget.LinearLayout.onMeasure(LinearLayout.java:721)
at android.view.View.measure(View.java:26411)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:7845)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
at com.android.internal.policy.DecorView.onMeasure(DecorView.java:1050)
at android.view.View.measure(View.java:26411)
at android.view.ViewRootImpl.performMeasure(ViewRootImpl.java:3635)
It's clear I'm missing something, but I have no idea what.
I'm struggling with both a dilemma and an issue here. The dilemma is that I want to keep the ManagerUsers screen simple, but Google is pushing fragments. I can see a use for fragments for this screen later on, but for now, it seems like overkill. Should I use them anyway?
Second, if I do avoid fragments for now, I am struggling to finish setting up the ClickListener to simply change the name of the user in the ListView. Kotlin seems to have at least 6 different methods for doing most things, making it confusing to figure this out. Google's tutorials force you to use fragments, and others' tutorials don't really cover what I'm trying to do. When I navigate to the Manage Users activity, the app crashes. LogCat shows that userSelect needs to be initialized. I have no idea how to do that.
UserListAdapter.kt
package com.neillbarrett.debitsandcredits
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.neillbarrett.debitsandcredits.database.UsersTable
import com.neillbarrett.debitsandcredits.databinding.ActivityManageUsersBinding
class UserListAdapter(private val userSelect: (UsersTable?) -> Unit) :
ListAdapter<UsersTable, UserListAdapter.UserViewHolder>(UsersComparator()) {
override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
val current = getItem(position)
holder.bind(current, userSelect)
}
class UserViewHolder(private val binding: ActivityManageUsersBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(usersTable: UsersTable?, userSelect: (UsersTable?) -> Unit) {
binding.root.setOnClickListener( View.OnClickListener {
userSelect(usersTable)
binding.etEditName.setText(R.layout.activity_list_of_users.toString())
})
}
companion object {
fun create(parent: ViewGroup) : UserViewHolder {
val view: View = LayoutInflater.from(parent.context)
.inflate(R.layout.activity_manage_users, parent, false)
return UserViewHolder(ActivityManageUsersBinding.bind(view))
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.activity_manage_users, parent, false)
return UserViewHolder.create(parent)
}
class UsersComparator : DiffUtil.ItemCallback<UsersTable>() {
override fun areItemsTheSame(oldItem: UsersTable, newItem: UsersTable): Boolean {
return oldItem == newItem
}
override fun areContentsTheSame(oldItem: UsersTable, newItem: UsersTable): Boolean {
return oldItem.userName == newItem.userName
}
}
}
ManageUsers.kt
package com.neillbarrett.debitsandcredits
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.text.TextUtils
import android.widget.Button
import android.widget.EditText
import android.widget.Toast
import androidx.activity.viewModels
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.neillbarrett.debitsandcredits.database.CreditsAndDebitsApp
import com.neillbarrett.debitsandcredits.database.UsersTable
import com.neillbarrett.debitsandcredits.databinding.ActivityManageUsersBinding
class ManageUsers : AppCompatActivity() {
lateinit var binding: ActivityManageUsersBinding
lateinit var recyclerView: RecyclerView
lateinit var editTextAddUser: EditText
lateinit var editTextChangeUser: EditText
lateinit var newUser: String
lateinit var userSelect: ((UsersTable?) -> Unit)
var position: Long = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityManageUsersBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
//setContentView(R.layout.activity_manage_users)
val userViewModel: UserViewModel by viewModels {
UserViewModelFactory((application as CreditsAndDebitsApp).repository)
}
recyclerView = findViewById(R.id.rec_view_userList)
editTextAddUser = findViewById(R.id.et_UserName)
editTextChangeUser = findViewById(R.id.et_Edit_Name)
val adapter = UserListAdapter(userSelect)
binding.recViewUserList.adapter = adapter
recyclerView.adapter = adapter
recyclerView.layoutManager = LinearLayoutManager(this)
userViewModel.allUsers.observe(this, Observer() {user ->
user?.let { adapter.submitList(it) }
})
val btnAddUser = findViewById<Button>(R.id.btn_AddUser)
btnAddUser.setOnClickListener {
if (TextUtils.isEmpty(editTextAddUser.text)) {
Toast.makeText(this, "User name cannot be empty", Toast.LENGTH_SHORT).show()
} else {
newUser = editTextAddUser.text.toString()
userViewModel.insertUser(UsersTable(0, newUser))
}
}
val btnChangeUser = findViewById<Button>(R.id.btn_ChangeUserName)
btnChangeUser.setOnClickListener {
if (recyclerView.getChildAdapterPosition(it) == -1) {
Toast.makeText(this, "Select a name.", Toast.LENGTH_SHORT).show()
} else {
if (editTextChangeUser.text.toString() == recyclerView.adapter.toString()) {
Toast.makeText(this, "Name has not been changed.", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(this, "Name would have been changed.", Toast.LENGTH_SHORT).show()
/*val rvItemRecId: Long
rvItemRecId = adapter.getItemId(position.toInt())
userViewModel.updateUser(UsersTable(rvItemRecId.toInt(), adapter.toString()))*/
}
}
}
}
}
UserViewModel.kt
package com.neillbarrett.debitsandcredits
import androidx.lifecycle.*
import com.neillbarrett.debitsandcredits.database.UsersTable
import kotlinx.coroutines.launch
import java.lang.IllegalArgumentException
class UserViewModel(private val repository: UserRepository) : ViewModel() {
val allUsers: LiveData<List<UsersTable>> = repository.allUsers.asLiveData()
fun insertUser(user: UsersTable) = viewModelScope.launch {
repository.insertUser(user)
}
fun updateUser(user: UsersTable) = viewModelScope.launch {
repository.updateUser(user)
}
fun deleteUser(user: UsersTable) = viewModelScope.launch {
repository.deleteUser(user)
}
}
class UserViewModelFactory(private val repository: UserRepository) : ViewModelProvider.Factory{
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(UserViewModel::class.java)) {
#Suppress("UNCHECKED_CAST")
return UserViewModel(repository) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
If you check your ManageUsers activity implementation
lateinit var userSelect: ((UsersTable?) -> Unit)
userSelect is lateinit and it must be initialised before you pass the reference to UserListAdapter adapter. Something like this
userSelect = {
// do something
}
before passing to adapter, or
val userSelect: ((UsersTable?) -> Unit) = {
}
at the time of declaration.
I'm trying to implement a DialogPreference in a Preference activity. I achieved it but it seems setTargetFragment is marked as deprecated.
Here is my old code:
override fun onDisplayPreferenceDialog(preference: Preference?) {
val clearStatsDialog = preference as? DialogPreferenceClearStats
if (clearStatsDialog != null) {
val dialogFragment = DialogPrefCompat.newInstance(clearStatsDialog.key)
dialogFragment.setTargetFragment(this, 0)
dialogFragment.positiveResult = {
Toast.makeText(activity, "yes", Toast.LENGTH_LONG).show()
}
dialogFragment.show(this.parentFragmentManager, null)
} else {
super.onDisplayPreferenceDialog(preference)
}
}
I wanted to replace it with setFragmentResultListener but I'm always getting "Target fragment must implement TargetFragment interface" exception.
Can someone help me?
Here is the complete code:
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.util.AttributeSet
import android.view.View
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.preference.DialogPreference
import androidx.preference.Preference
import androidx.preference.PreferenceDialogFragmentCompat
import androidx.preference.PreferenceFragmentCompat
import com.workout.intervaltimer.R
import com.workout.intervaltimer.util.WorkoutTimerUtil
class DialogPreferenceClearStats(context: Context, attrs: AttributeSet?) : DialogPreference(context, attrs), DialogPreference.TargetFragment {
override fun <T : Preference?> findPreference(key: CharSequence): T? {
TODO("Not yet implemented")
}
}
class DialogPrefCompat : PreferenceDialogFragmentCompat() {
lateinit var positiveResult: ()->Unit
override fun onDialogClosed(positiveResult: Boolean) {
if (positiveResult) {
positiveResult()
}
}
companion object {
fun newInstance(key: String): DialogPrefCompat {
val fragment = DialogPrefCompat()
val bundle = Bundle(1)
bundle.putString(PreferenceDialogFragmentCompat.ARG_KEY, key)
fragment.arguments = bundle
return fragment
}
}
}
class SettingsActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.settings_activity)
if (savedInstanceState == null) {
supportFragmentManager
.beginTransaction()
.replace(R.id.settings, SettingsFragment())
.commit()
}
supportActionBar?.setDisplayHomeAsUpEnabled(true)
}
class SettingsFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.root_preferences, rootKey)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
this.parentFragmentManager.setFragmentResultListener("requestKey", viewLifecycleOwner) { requestKey, bundle ->
val dialogFragment = DialogPrefCompat.newInstance("clearStatsDialog.key")
dialogFragment.positiveResult = {
Toast.makeText(activity, "yes", Toast.LENGTH_LONG).show()
}
dialogFragment.show(this.parentFragmentManager, null)
}
}
override fun onDisplayPreferenceDialog(preference: Preference?) {
val clearStatsDialog = preference as? DialogPreferenceClearStats
if (clearStatsDialog != null) {
this.parentFragmentManager.setFragmentResult("requestKey", Bundle())
}
}
override fun onPreferenceTreeClick(preference: Preference?): Boolean {
//It should be possible to launchs activities and websites from xml intent but I didn't achieve it.
when (preference!!.key) {
getString(R.string.force_dark_theme) -> {
WorkoutTimerUtil.setDayNightThemeForApp(requireActivity().applicationContext)
}
getString(R.string.third_party_software) -> {
val intent = Intent(Intent.ACTION_VIEW)
intent.data = Uri.parse(WEB_THIRD_PARTY_SOFTWARE)
startActivity(intent)
}
getString(R.string.terms_conditions) -> {
val intent = Intent(Intent.ACTION_VIEW)
intent.data = Uri.parse(WEB_TERMS)
startActivity(intent)
}
getString(R.string.privacy_policy) -> {
val intent = Intent(Intent.ACTION_VIEW)
intent.data = Uri.parse(WEB_PRIVACY_POLICY)
startActivity(intent)
}
}
return super.onPreferenceTreeClick(preference)
}
}
companion object {
const val WEB_THIRD_PARTY_SOFTWARE = "https://sites.google.com/view/that-third-party-software"
const val WEB_TERMS = "https://sites.google.com/view/that-terms-conditions"
const val WEB_PRIVACY_POLICY = "https://sites.google.com/view/that-privacy-policy"
}
}
Thanks in advance.
Doesn't look too promising: https://android-review.googlesource.com/c/platform/frameworks/support/+/1843122/8/preference/preference/src/main/java/androidx/preference/PreferenceDialogFragmentCompat.java just added #SuppressWarnings("deprecation") over the relevant code. I think that until PreferenceDialogFragmentCompat is switched over to the new API we're stuck calling setTargetFragment :(
I think the such behavour is related to the next Android bug:
https://issuetracker.google.com/issues/181793702
It still not fixed for Preferences lib 1.2.0 (Nov 1, 2022).
setTargetFragment() must be called even if lint says it is deprecated.
https://github.com/reticent-monolith/winds_server is the github repo if anyone finds it easier looking there.
I'm trying to use KMongo and Ktor with Kotlin's Serialization module but creating the MongoClient results in the following exception:
java.lang.ClassCastException: class org.litote.kmongo.serialization.SerializationCodecRegistry cannot be cast to class org.bson.codecs.configuration.CodecProvider (org.litote.kmongo.serialization.SerializationCodecRegistry and org.bson.codecs.configuration.CodecProvider are in unnamed module of loader 'app')
at org.litote.kmongo.service.ClassMappingTypeService$DefaultImpls.codecRegistry(ClassMappingTypeService.kt:83)
at org.litote.kmongo.serialization.SerializationClassMappingTypeService.codecRegistry(SerializationClassMappingTypeService.kt:40)
at org.litote.kmongo.serialization.SerializationClassMappingTypeService.coreCodecRegistry(SerializationClassMappingTypeService.kt:97)
at org.litote.kmongo.service.ClassMappingType.coreCodecRegistry(ClassMappingType.kt)
at org.litote.kmongo.service.ClassMappingTypeService$DefaultImpls.codecRegistry$default(ClassMappingTypeService.kt:79)
at org.litote.kmongo.KMongo.configureRegistry$kmongo_core(KMongo.kt:79)
at org.litote.kmongo.KMongo.createClient(KMongo.kt:70)
at org.litote.kmongo.KMongo.createClient(KMongo.kt:57)
at org.litote.kmongo.KMongo.createClient(KMongo.kt:47)
at org.litote.kmongo.KMongo.createClient(KMongo.kt:39)
at com.reticentmonolith.repo.MongoDispatchRepo.<init>(MongoDispatchRepo.kt:11)
at com.reticentmonolith.ApplicationKt.<clinit>(Application.kt:14)
... 23 more
Am I missing something in my build.gradle?
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
}
}
plugins {
id 'org.jetbrains.kotlin.jvm' version '1.5.10'
id 'org.jetbrains.kotlin.plugin.serialization' version '1.5.10'
id 'application'
}
group 'com.reticentmonolith'
version '0.0.1-SNAPSHOT'
mainClassName = "io.ktor.server.netty.EngineMain"
sourceSets {
main.kotlin.srcDirs = main.java.srcDirs = ['src']
test.kotlin.srcDirs = test.java.srcDirs = ['test']
main.resources.srcDirs = ['resources']
test.resources.srcDirs = ['testresources']
}
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
// KOTLIN
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
// KTOR
implementation "io.ktor:ktor-server-netty:$ktor_version"
implementation "ch.qos.logback:logback-classic:$logback_version"
implementation "io.ktor:ktor-server-core:$ktor_version"
testImplementation "io.ktor:ktor-server-tests:$ktor_version"
implementation "io.ktor:ktor-serialization:$ktor_version"
// MONGO
implementation group: 'org.mongodb', name: 'mongo-java-driver', version: '3.12.8'
// KOTLINX SERIALIZATION
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.2.1"
// KMONGO
implementation 'org.litote.kmongo:kmongo-serialization:4.2.7'
implementation group: 'org.litote.kmongo', name: 'kmongo-id-serialization', version: '4.2.7'
// KBSON (optional dependency for KMONGO and Serialization)
implementation "com.github.jershell:kbson:0.4.4"
}
Here's my MongoClient class:
import com.mongodb.client.MongoDatabase
import com.reticentmonolith.models.Dispatch
import java.time.LocalDate
import org.litote.kmongo.*
class MongoDispatchRepo: DispatchRepoInterface {
private val client = KMongo.createClient()
private val database: MongoDatabase = client.getDatabase("zw")
private val windsData = database.getCollection<Dispatch>("winds")
override fun createDispatch(dispatch: Dispatch) {
windsData.insertOne(dispatch)
}
override fun getAllDispatches(): Map<String, List<Dispatch>> {
val list = windsData.find().toList()
return mapOf("dispatches" to list)
}
override fun getDispatchesByDate(date: LocalDate): Map<String, List<Dispatch>> {
return mapOf("dispatches" to windsData.find(Dispatch::date eq date).toList())
}
override fun getDispatchesByDateRange(start: LocalDate, end: LocalDate): Map<String, List<Dispatch>> {
return mapOf("dispatches" to windsData.find(Dispatch::date gte(start), Dispatch::date lte(end)).toList())
}
override fun getDispatchById(id: Id<Dispatch>): Dispatch? {
return windsData.findOneById(id)
}
override fun updateDispatchById(id: Id<Dispatch>, update: Dispatch): Boolean {
val oldDispatch = getDispatchById(id)
if (oldDispatch != null) {
update.date = oldDispatch.date
update._id = oldDispatch._id
windsData.updateOneById(id, update)
return true
}
return false
}
override fun updateLastDispatch(update: Dispatch): Boolean {
val lastDispatch = getLastDispatch() ?: return false
update.date = lastDispatch.date
update._id = lastDispatch._id
windsData.updateOneById(lastDispatch._id, update)
return true
}
override fun deleteDispatchById(id: Id<Dispatch>) {
windsData.deleteOne(Dispatch::_id eq id)
}
override fun getLastDispatch(): Dispatch? {
val todaysDispatches = getDispatchesByDate(LocalDate.now())
if (todaysDispatches.isEmpty()) return null
return todaysDispatches["dispatches"]?.last()
}
override fun addSpeedsToLastDispatch(line4: Int?, line3: Int?, line2: Int?, line1: Int?) {
val lastDispatch = getLastDispatch() ?: return
lastDispatch.riders.get(4)?.speed = line4
lastDispatch.riders.get(3)?.speed = line3
lastDispatch.riders.get(2)?.speed = line2
lastDispatch.riders.get(1)?.speed = line1
updateLastDispatch(lastDispatch)
}
fun purgeDatabase() {
windsData.deleteMany(Dispatch::comment eq "")
}
}
And my Application.kt for Ktor:
package com.reticentmonolith
import com.reticentmonolith.models.createExampleDispatches
import io.ktor.application.*
import io.ktor.response.*
import io.ktor.routing.*
import io.ktor.http.*
import com.reticentmonolith.repo.MongoDispatchRepo
import io.ktor.features.*
import io.ktor.serialization.*
import kotlinx.serialization.json.Json
import org.litote.kmongo.id.serialization.IdKotlinXSerializationModule
val repo = MongoDispatchRepo()
fun main(args: Array<String>) = io.ktor.server.netty.EngineMain.main(args)
#Suppress("unused") // Referenced in application.conf
#kotlin.jvm.JvmOverloads
fun Application.module(testing: Boolean = false) {
install(ContentNegotiation) {
json(
Json { serializersModule = IdKotlinXSerializationModule },
contentType = ContentType.Application.Json
)
}
repo.purgeDatabase()
val dispatchList = repo.getAllDispatches()["dispatches"]
if (dispatchList != null && dispatchList.isEmpty()) {
createExampleDispatches(0).forEach {
repo.createDispatch(it)
println("######## date: ${it.date} ################")
}
}
routing {
get("/dispatches/") {
call.response.status(HttpStatusCode.OK)
val dispatches = repo.getAllDispatches()
println("Dispatches from Mongo: $dispatches")
println("Dispatches encoded for response: $dispatches")
}
}
}
And finally the class being serialized:
package com.reticentmonolith.models
import com.reticentmonolith.serializers.DateSerializer
import com.reticentmonolith.serializers.TimeSerializer
import kotlinx.serialization.Contextual
import org.litote.kmongo.*
import kotlinx.serialization.Serializable
import java.time.LocalDate
import java.time.LocalTime
#Serializable
data class Dispatch(
var riders: MutableMap<Int, #Contextual Rider?> = mutableMapOf(
1 to null,
2 to null,
3 to null,
4 to null
),
var comment: String = "",
var wind_degrees: Int,
var wind_speed: Double,
var winds_instructor: String,
var bt_radio: String,
var _id: #Contextual Id<Dispatch> = newId(),
// #Serializable(with=DateSerializer::class)
#Contextual var date: LocalDate = LocalDate.now(),
// #Serializable(with=TimeSerializer::class)
#Contextual var time: LocalTime = LocalTime.now()
)
fun createExampleDispatches(amount: Int): Collection<Dispatch> {
val dispatches = mutableListOf<Dispatch>()
for (i in 0..amount) {
dispatches.add(Dispatch(
bt_radio = "BT Instructor",
winds_instructor = "Winds Instructor",
wind_speed = 20.5,
wind_degrees = 186
).apply {
this.riders[2] = Rider(67, front_slider = Slider.BLACK, trolley = 125)
this.riders[3] = Rider(112, front_slider = Slider.NEW_RED, rear_slider = Slider.YELLOW, trolley = 34)
})
}
return dispatches
}
Any help would be hugely appreciated :) Thanks!
I'm writing Espresso UI test which mocks viewModel, referring GithubBrowserSample
what is the use of "TaskExecutorWithIdlingResourceRule", declaring Junit Rule will take care of IdlingResource?
Even after referring this "TaskExecutorWithIdlingResourceRule" class in my project whenever I build, compiler doesn't throw any error but when I run the test case it shows the Unresolved Error(s)
TaskExecutorWithIdlingResourceRule.kt
import androidx.arch.core.executor.testing.CountingTaskExecutorRule
import androidx.test.espresso.IdlingRegistry
import androidx.test.espresso.IdlingResource
import org.junit.runner.Description
import java.util.UUID
import java.util.concurrent.CopyOnWriteArrayList
import java.util.concurrent.TimeUnit
class TaskExecutorWithIdlingResourceRule : CountingTaskExecutorRule() {
// give it a unique id to workaround an espresso bug where you cannot register/unregister
// an idling resource w/ the same name.
private val id = UUID.randomUUID().toString()
private val idlingResource: IdlingResource = object : IdlingResource {
override fun getName(): String {
return "architecture components idling resource $id"
}
override fun isIdleNow(): Boolean {
return this#TaskExecutorWithIdlingResourceRule.isIdle
}
override fun registerIdleTransitionCallback(callback: IdlingResource.ResourceCallback) {
callbacks.add(callback)
}
}
private val callbacks = CopyOnWriteArrayList<IdlingResource.ResourceCallback>()
override fun starting(description: Description?) {
IdlingRegistry.getInstance().register(idlingResource)
super.starting(description)
}
override fun finished(description: Description?) {
drainTasks(10, TimeUnit.SECONDS)
callbacks.clear()
IdlingRegistry.getInstance().unregister(idlingResource)
super.finished(description)
}
override fun onIdle() {
super.onIdle()
for (callback in callbacks) {
callback.onTransitionToIdle()
}
}
}
Mocktest
#RunWith(AndroidJUnit4::class)
class MockTest {
#Rule
#JvmField
var activityRule = IntentsTestRule(SingleFragmentActivity::class.java, true, true)
#Rule
#JvmField
val executorRule = TaskExecutorWithIdlingResourceRule()
private lateinit var viewModel: SeriesFragmentViewModel
private val uiModelList = mutableListOf<SeriesBaseUIModel>()
private val seriesMutableLiveData = MutableLiveData<List<SeriesBaseUIModel>>()
private val seriesFragment = SeriesFragment()
#Before
fun init(){
viewModel = mock(SeriesFragmentViewModel::class.java)
`when`(viewModel.seriesLiveData).thenReturn(seriesMutableLiveData)
ViewModelUtil.createFor(viewModel)
activityRule.activity.setFragment(seriesFragment)
EspressoTestUtil.disableProgressBarAnimations(activityRule)
}
#Test
fun testLoading()
{
//Thread.sleep(3000)
uiModelList.add(ProgressUIModel())
seriesMutableLiveData.postValue(uiModelList.toList())
onView(withId(R.id.pod_series_recycler_view))
.check(selectedDescendantsMatch(withId(R.id.pod_adapter_series_header_title), isDisplayed()))
onView(withId(R.id.pod_series_recycler_view))
.check(selectedDescendantsMatch(withId(R.id.pod_adapter_series_header_title), withText(R.string.pod_series_header_title_text)))
onView(withId(R.id.pod_series_recycler_view))
.check(selectedDescendantsMatch(withId(R.id.pod_adapter_series_header_description), isDisplayed()))
onView(withId(R.id.pod_series_recycler_view))
.check(selectedDescendantsMatch(withId(R.id.pod_adapter_series_header_title), withText("Hello")))
Thread.sleep(5000)
}
}