0

I got the following Room database and want to output the name of a random user in a textview. Unfortunately running the code yields the output: kotlin.unit inside the textview. My files look like that:

MainActivity.kt

class MainActivity : AppCompatActivity() {

    private lateinit var mNameViewModel: NameViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        mNameViewModel = ViewModelProvider(this).get(NameViewModel::class.java)

        val btn = findViewById<Button>(R.id.btn_addName)
        val tv = findViewById<TextView>(R.id.tv_showName)

        btn.setOnClickListener {
            val text = findViewById<EditText>(R.id.et_enterName)?.text.toString()
            val name = Name(0, text)

            // Add Data to Database
            mNameViewModel.addName(name)
            Toast.makeText(applicationContext, "Successfully added $text.", Toast.LENGTH_LONG).show()

            val randomName = mNameViewModel.getRandomName()

            // Without .toString() I get an error, with it it displays kotlin.unit
            tv.text = randomName.toString() 
        }

    }
}

activity_main.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">

    <TextView
        android:id="@+id/tv_showName"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintHorizontal_bias="0.498"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.133" />

    <EditText
        android:id="@+id/et_enterName"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:ems="10"
        android:inputType="textPersonName"
        android:hint="Name"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.497"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.244" />

    <Button
        android:id="@+id/btn_addName"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button"
        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.379" />

</androidx.constraintlayout.widget.ConstraintLayout>

Name.kt

@Entity(tableName = "name_data")
data class Name (
    @PrimaryKey(autoGenerate = true) val id: Int,
    @ColumnInfo(name = "name") val name: String
)

NameDao.kt

@Dao
interface NameDao {

    @Insert
    fun addName(name: Name)

    @Query("SELECT name FROM name_data ORDER BY RANDOM() LIMIT 1")
    fun getRandomName(): String
}

NameDatabase.kt

@Database(entities = [Name::class], version = 1, exportSchema = false)
abstract class NameDatabase: RoomDatabase() {

    abstract fun nameDao(): NameDao

    companion object{
        @Volatile
        private var INSTANCE: NameDatabase? = null

        fun getDatabase(context: Context): NameDatabase{
            val tempInstance = INSTANCE
            if(tempInstance != null){
                return tempInstance
            }
            synchronized(this){
                val instance = databaseBuilder(
                    context.applicationContext,
                    NameDatabase::class.java,
                    "name_data"
                ).build()
                INSTANCE = instance
                return instance
            }
        }
    }
}

NameRepository.kt

class NameRepository(private val nameDao: NameDao) {

     fun getRandomName() { nameDao.getRandomName() }

     fun addName(name: Name) { nameDao.addName(name) }
}

NameViewModel.kt

class NameViewModel(application: Application): AndroidViewModel(application) {

    private val repository: NameRepository

    init {
        val nameDao = NameDatabase.getDatabase(application).nameDao()
        repository = NameRepository(nameDao)
    }

    fun addName(name: Name) {
        viewModelScope.launch(Dispatchers.IO){
            repository.addName(name)
        }
    }

    fun getRandomName() {
        viewModelScope.launch(Dispatchers.IO){
            repository.getRandomName()
        }
    }
}

This is how the output of textview when pressing the button.

How it looks when pressing the button

The database gets populated though.

Database gets populated

Apreciate any help to get the data displayed. Thank you!

Cion
  • 85
  • 8

2 Answers2

1

The issue is that in your getRandomNumber method inside viewmodel you don't return anything that's why you get kotlin.unit . You should instead make sure to return a value

A possible solution would be the following.

  1. Create a method inside your dao which takes a number and returns that row (this will be essentially the random name)
  2. From your repo call that method with a random number you could use Random.getNextInt not sure how do you get a random
  3. From your view model call that method
  4. From your button onClick call the viewModel method

Make sure to use suspend where applicable in order to get a result. For the case that I showcased above that would be to launch a coroutine in view level and make the rest of the calls (vm,repo,dao) suspend

georkost
  • 588
  • 6
  • 12
  • Thank you for your answer. Do you know how I would go about that in my case? Or do you have some sort of example if possible? – Cion Jan 08 '21 at 10:24
  • Tyvm for the update, unfortunately I still don't quiet understand where exactly my mistake is. From your first and second point I don't understand why I have to make my dao and methods take random numbers since I already have this query ```@Query("SELECT name FROM name_data ORDER BY RANDOM() LIMIT 1")``` to do that for me. I then proceeded to try at least your 3rd and 4th point and tried starting a coroutine from my view level like this: ```lifecycleScope.launch(Dispatchers.IO) ``` but this unfortunately gives me again a Unit type as a result and using the toString() method ouputs wrong name – Cion Jan 08 '21 at 11:22
  • Ah so sorry didn't notice you already implemented some kind of random logic. Okay so the only thing you need to do is to make sure `getRandomName` inside view model returns some value by making it suspend – georkost Jan 08 '21 at 14:15
  • I suspended the viewmodel method but for some reason I still don't get the desired output. The method still doesn't return a String but like before a Unit. Any advice? – Cion Jan 08 '21 at 15:50
  • Are you having it like this? `suspend fun getRandomName() = repository.getRandomName() ` – georkost Jan 08 '21 at 16:06
  • Yes I got it exactly like that. I tried another method which seems to be working, and I will post it here in second, but in case you know how to get it working like that I'm interested in that too. – Cion Jan 08 '21 at 16:20
0

So after some testing and back and forth this is what I have and it seems to be working. If anyone has some things to bring in I gladly accept them and a big thanks to georkost for all the usefull tips!

// No changes made to database

@Entity(tableName = "name_data")
data class Name (
    @PrimaryKey(autoGenerate = true) val id: Int,
    @ColumnInfo(name = "name") val name: String
)

//*********************************************************************************

// Changed return type from Query to LiveData<String> (was just String before)

@Dao
interface NameDao {

    @Insert
    fun addName(name: Name)

    @Query("SELECT name FROM name_data ORDER BY RANDOM() LIMIT 1")
    fun getRandomName(): LiveData<String>    // HERE
}

//*********************************************************************************

// Changed getRandomMeal from fun to val

class NameRepository(private val nameDao: NameDao) {

     val getRandomMeal: LiveData<String> = nameDao.getRandomName()  // HERE

     fun addName(name: Name) { nameDao.addName(name) }
}

//*********************************************************************************

// Added getRandomName val, initialized it and removed the fun

class NameViewModel(application: Application): AndroidViewModel(application) {

    val getRandomName: LiveData<String>             // HERE
    private val repository: NameRepository

    init {
        val nameDao = NameDatabase.getDatabase(application).nameDao()
        repository = NameRepository(nameDao)
        getRandomName = repository.getRandomMeal    // HERE
    }

    fun addName(name: Name) {
        viewModelScope.launch(Dispatchers.IO){
            repository.addName(name)
        }
    }
}

//*********************************************************************************

// Changed the last row to observe the LiveData and display it in the Text View

class MainActivity : AppCompatActivity() {

    private lateinit var mNameViewModel: NameViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        mNameViewModel = ViewModelProvider(this).get(NameViewModel::class.java)

        val btn = findViewById<Button>(R.id.btn_addName)
        val tv = findViewById<TextView>(R.id.tv_showName)

        btn.setOnClickListener {
            val text = findViewById<EditText>(R.id.et_enterName)?.text.toString()
            val name = Name(0, text)

            // Add Data to Database
            mNameViewModel.addName(name)
            Toast.makeText(applicationContext, "Successfully added $text.", Toast.LENGTH_LONG).show()

            // HERE
            mNameViewModel.getRandomName.observe(this, Observer { String -> tv.text = String })


        }
    }
}
Cion
  • 85
  • 8