Why is my data size zero from repository in Kotlin? - kotlin

I m trying to pull data from room database by identifier for my dictionary app in Kotlin jetpack compose but when I try to show data by identifier from room database there is no data because data size is coming zero. It may be because I'm pulling the data asynchronously, but shouldn't it still arrive? Is there a place I missed? can you help me ? I share my code below .
My Room Entity class
#Entity(tableName = "GermanDictionary")
data class DictionaryEntity(
#PrimaryKey(autoGenerate = true) val uid: Int? = null,
#ColumnInfo(name = "identifier") val identifier:String,
#ColumnInfo(name = "words") val words:List<Word>
)
My Dao
#Dao
interface DictionaryDao {
#Query("SELECT * FROM GermanDictionary")
suspend fun getAllFromDatabase(): List<DictionaryEntity>
#Query("SELECT * FROM GermanDictionary WHERE uid =:id ")
suspend fun searchWordFromDatabase(id:String):List<DictionaryEntity>
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertGermanWords(GermanWords:List<DictionaryEntity>)
#Query("SELECT * FROM GermanDictionary WHERE identifier =:identifier ")
suspend fun getWordsByIdentifier(identifier:String):DictionaryEntity
}
database
#TypeConverters(DictionaryConverter::class)
#Database(entities = [DictionaryEntity::class], version = 1)
abstract class DictionaryDatabase : RoomDatabase(){
abstract val dao: DictionaryDao
}
Domain
Repository
interface DictionaryRepository {
suspend fun getAllFromDatabase(): Flow<List<Word>>
suspend fun searchWordFromDatabase(id:String): Word
suspend fun insertGermanWords()
suspend fun getWordsByIdentifier(identifier:String) : Flow<List<Word>?>
}
Domain
UseCase
class GetWordsByIdentifierUseCase #Inject constructor(
private val repository: DictionaryRepository
){
suspend fun getWordsByIdentifier(identifier : String) : Flow<List<Word>?> {
return repository.getWordsByIdentifier(identifier = identifier)
}
}
ui
View Model
#HiltViewModel
class DisplayScreenViewModel #Inject constructor(
savedStateHandle: SavedStateHandle,
private var getWordsByIdentifierUseCase: GetWordsByIdentifierUseCase
) : ViewModel() {
private var _state = mutableStateOf(WordState())
var state: State<WordState> = _state
private val identifier: String? = savedStateHandle[DISPLAY_ARG_KEY]
init {
identifier?.let { load(identifier = this.identifier) }
}
private fun load(identifier: String) {
viewModelScope.launch {
getWordsByIdentifierUseCase.getWordsByIdentifier(identifier)
.collect { item ->
_state.value = state.value.copy(
words = item
)
}
}
}
}
ui
DisplayWordScreen
#Composable
fun DisplayScreen(
navController: NavController,
viewModel: DisplayScreenViewModel = hiltViewModel()) {
val state = viewModel.state.value
BaseBottomBar(navController = navController){
LazyColumn(modifier = Modifier.fillMaxSize()){
items(state.words?.size!!){index->
Item(word = state.words[index])
}
}
}
}
WordState
data class WordState(
val words : List<Word>? = emptyList()
)
I go to the DisplayScreen when I click identifier tag in first screen and I passed identifier first screen to Display screen and I fetch words data according to identifier in Display Screen ViewModel using this identifier which I passed from first screen.
ERROR
java.lang.NullPointerException
at com.example.almancasozluk.dictionaryfeature.ui.worddisplayscreen.DisplayScreenKt$DisplayScreen$1$1.invoke(DisplayScreen.kt:23)
at com.example.almancasozluk.dictionaryfeature.ui.worddisplayscreen.DisplayScreenKt$DisplayScreen$1$1.invoke(DisplayScreen.kt:22)

Related

how can I convert my room entity to my data class in kotlin?

I have a data class that I pull from internet and I want to save room database but there is a problem like that.
It always gives an error like this, how can I overcome this problem?
my room entity class
#Entity(tableName = "ExchangeValues")
data class ExchangeEntity(
#ColumnInfo(name = "base_code") val base_code: String,
#ColumnInfo(name = "conversion_rates") val conversion_rates: ConversionRates,
#ColumnInfo(name = "result") val result: String,
#PrimaryKey(autoGenerate = true) val uid:Int?=null
)
my dao
#Dao
interface ExchangeDao {
#Query("SELECT * FROM ExchangeValues")
suspend fun getAll() : List<ExchangeEntity>
#Query("UPDATE ExchangeValues SET base_code=:base_code,conversion_rates=:conversion_rates , result=:result")
suspend fun update(base_code:String,conversion_rates:ConversionRates,result:String)
}
my exchange data class
#Serializable
data class Exchange(
val base_code: String,
val conversion_rates: ConversionRates,
val documentation: String,
val result: String,
val terms_of_use: String,
val time_last_update_unix: Int,
val time_last_update_utc: String,
val time_next_update_unix: Int,
val time_next_update_utc: String
) {
fun toEntity() = ExchangeEntity(
base_code = base_code,
conversion_rates = conversion_rates,
result = result
)
}
#Serializable
data class ConversionRates(
val conversionRates : Map<String,Double>
)
I cant use toEntity function in getAll()
exchangeRepositoryImpl
class ExchangeRepositoryImpl #Inject constructor(
private val dao:ExchangeDao
) : ExchangeRepository{
override suspend fun getAll() : Flow<List<Exchange>> {
return flow {
emit(dao.getAll())
}
}
override suspend fun update(exchange: Exchange) {
dao.update(exchange.base_code,exchange.result,exchange.conversion_rates)
}
}
my exchange converter
class ExchangeConverter {
#TypeConverter
fun fromSource(conversionRates: ConversionRates) : String{
val gson = Gson()
return gson.toJson(conversionRates)
}
#TypeConverter
fun toSource(json: String): ConversionRates {
val gson = Gson()
val typeToken = object : TypeToken<List<ConversionRates>>() {}.type
return Gson().fromJson(json, typeToken)
}
}
I wrote a converter like this, but it might not be correct, I'm not so sure. How can I solve this problem?
Inside flow you have created call map function the call to toEntity() eg
flow{
emit (dao.getAll().map{it.toEntity()})
}
Well your flow returns a flow of
List<Exchange>
and your repo returns
List<ExchangeEntity>
and there's nothing in your code to map an ExchangeEntity to an Exchange.
So you need something like:
override suspend fun getAll() : Flow<List<Exchange>> {
return flow {
emit(dao.getAll().map{Exchange(base_code = it.baseCode)})// add in other fields on exchange constructor
}
}

Kotlin - Error with SAM Interface (Android Routing Cicerone)

I started using the library for routes in an android application. There was such a problem, I don't know how to solve it.
Type mismatch.
Required:
Creator<Context, Intent>
Found:
() → Intent
Library used Cicerone. I created my object class Screens and according to some sources made an implementation like this
import com.csproject.rflex.app.App
import com.csproject.rflex.presentation.launch.LaunchActivity
import com.github.terrakok.cicerone.androidx.ActivityScreen
object Screens {
fun launch() = ActivityScreen {
LaunchActivity.newIntent(App.instance.getAppContext())
}
}
Activity code fragment
class LaunchActivity: ABaseActivity(), ILaunchView {
companion object{
fun newIntent(context: Context) = Intent(context, LaunchActivity::class.java)
}
Libriry class
sealed class AppScreen : Screen
fun interface Creator<A, R> {
fun create(argument: A): R
}
open class FragmentScreen #JvmOverloads constructor(
private val key: String? = null,
private val fragmentCreator: Creator<FragmentFactory, Fragment>
) : AppScreen() {
override val screenKey: String get() = key ?: super.screenKey
fun createFragment(factory: FragmentFactory) = fragmentCreator.create(factory)
}
open class ActivityScreen #JvmOverloads constructor(
private val key: String? = null,
private val intentCreator: Creator<Context, Intent>
) : AppScreen() {
override val screenKey: String get() = key ?: super.screenKey
open val startActivityOptions: Bundle? = null
fun createIntent(context: Context) = intentCreator.create(context)
}
UPDATE #1
I modified the code a bit, but I'm not sure if it should work this way
fun launch(context: Context) = ActivityScreen (intentCreator = object: Creator<Context, Intent> {
override fun create(argument: Context): Intent {
return MainActivity.newIntent(context)
}
})
It works, but I think you can do it differently
Your lambda is getting its context externally rather than from a lambda argument, so its signature doesn't match the signature of your interface.
Try this:
ActivityScreen { LaunchActivity.newIntent(it) }
If it isn't able to infer the context from that, I guess you would need:
ActivityScreen { context: Context -> LaunchActivity.newIntent(context) }

How to compare one table with name

Hi i have a searchbar on maps and I want it to compare when I search for a city if it is in my City Table
I wrote a Dao and a method for this but every time when I write a wrote a city that doesn't exist the apps crashed`
My data serviceinterface
fun getCitiesFindbyName(name: String, getName: GetName)
fun startBtn(view: View){
lateinit var location: String
val dataService: DataService = (requireActivity().application as MyApp).dataService
val searchView = view.findViewById<SearchView>(R.id.sv_location)
location = searchView.query.toString()
dataService.getCitiesFindbyName(location, this)
the callback
interface GetName {
fun onFinish( city: City)
}
and the dao
#Query ("SELECT * FROM City WHERE name = :name")
fun getCitybyId(name:String):City
and the map view fragment
fun startBtn(view: View){
lateinit var location: String
val dataService: DataService = (requireActivity().application as MyApp).dataService
val searchView = view.findViewById<SearchView>(R.id.sv_location)
location = searchView.query.toString()
dataService.getCitiesFindbyName(location, this)
override fun onFinish(city:City ) {
val dataService: DataService = (requireActivity().application as MyApp).dataService
if (city.name.isNotEmpty()){
val latLng = LatLng(city.coord.lat!!, city.coord.lon!!)
val lat= (city.coord.lat)
val long = (city.coord.lon)
val name =(city.name)
nMap.addMarker(MarkerOptions().position(latLng).title(city.name))
retrofitResponse2(lat,long,city.name)
nMap.animateCamera(CameraUpdateFactory.newLatLng(latLng))
dataService.getCitiesFindbyName(name, this)
}
else{
Toast.makeText(requireContext(), "There is no info about this city", Toast.LENGTH_LONG).show()
}
}
}

Cannot access elements with a specific attribute from my dao (Android Room)

I am making a toDoList app with multiple users. I have two entities. One is User and the other one is Task
#Entity(tableName = "tasks")
data class Task(
#PrimaryKey(autoGenerate = true)
val id : Int,
val taskUserId : Int,
val taskName : String,
val taskDescription : String,
var taskPriority : Int)
#Entity(tableName = "users")
data class User(
#PrimaryKey
val userId : Int,
val userName : String,
val password : String)
As you can see the two entities need to have one thing in common and that would be the users id so I could query for tasks based on the users id. This is what my dao looks like:
#Dao
interface UserDao {
#Query("SELECT * FROM users")
fun getAllUsers() : LiveData<List<User>>
#Query("SELECT * FROM tasks WHERE taskUserId LIKE :userId ORDER BY taskPriority DESC")
fun getAllUsersTasks(userId : Int) : LiveData<List<Task>>
#Insert
suspend fun saveUser(user : User)
#Insert
suspend fun saveTask(task : Task)
}
And here are the other model elements:
class TaskRepository(private val userDao: UserDao) {
var userId : Int = 0
val allTasks = userDao.getAllUsersTasks(userId)
val allUsers : LiveData<List<User>> = userDao.getAllUsers()
suspend fun saveUser(user : User){
userDao.saveUser(user)
}
suspend fun saveTask(task : Task){
userDao.saveTask(task)
}
}
And the ViewModel:
class MainViewModel(application: Application) : AndroidViewModel(application) {
private val taskRepository : TaskRepository
val allTasks : LiveData<List<Task>>
val allUsers : LiveData<List<User>>
private var userId : Int = 0
init {
val taskDao = UserTasksDatabase.getDatabase(application)!!.userDao()
taskRepository = TaskRepository(taskDao)
allTasks = taskRepository.allTasks
allUsers = taskRepository.allUsers
taskRepository.userId = this.userId
}
fun saveUser(user: User) = viewModelScope.launch(Dispatchers.IO){
taskRepository.saveUser(user)
}
fun saveTask(task: Task) = viewModelScope.launch(Dispatchers.IO){
taskRepository.saveTask(task)
}
fun setUserId(id : Int){
this.userId = id
}
fun getUserId() : Int{
return this.userId
}
}
When the user pushes the login or the sign up button I set the user's id in the viewModel
logInButton.setOnClickListener {
val userName: String = userNameEditText.text.toString()
val password: String = passwordEditText.text.toString()
val id: Int = IdMaker.generateId(userName)
val user = User(id, userName, password)
if(listOfAllUsers.contains(user)){
sharedViewModel.setUserId(id)
Toast.makeText(requireContext(), "Welcome $userName", Toast.LENGTH_LONG).show()
fragmentManager!!.beginTransaction().apply {
replace(R.id.fl_activity_main, MainFragment())
commit()
}
}else{
Toast.makeText(requireContext(), "The user already exists", Toast.LENGTH_LONG).show()
}
}
But when i try to access the users in the main fragment and set them on my recyclerview I get nothing. And I can't really see what am I missing.
sharedViewModel.allTasks.observe(this, Observer {tasks ->
tasks.let { adapter.setTasks(it) }
})
I'm sorry if a question is a bit simple but I hope I will gain some insight in what am I missing.
Side note: The recyclerview is set up properly and tested.
I think you are not setting the TaskRepository.userId correctly.
class MainViewModel(application: Application) : AndroidViewModel(application) {
private val taskRepository : TaskRepository
private var userId : Int = 0
init {
...
// init is called only once at the start of this viewModel's instance creation
// Here, you set your repository's userId to 0
taskRepository.userId = this.userId
}
}
At the instance creation of your MainViewModel, you set the userId of its TaskRepository instance to 0, which in your following codes is not changed anymore. Upon your login button click, you do call:
sharedViewModel.setUserId(id)
but the function alters only the viewModel's userId property, not the userId property of your TaskRepository instance. So try:
class MainViewModel(application: Application) : AndroidViewModel(application) {
private lateinit var taskRepository : TaskRepository
val allTasks = taskRepository.allTasks
val allUsers = taskRepository.allUsers
// remove init {}
fun setUserId(id : Int){
// set up the repository only after userId is known
val taskDao = UserTasksDatabase.getDatabase(application)!!.userDao()
taskRepository = TaskRepository(taskDao, id)
}
}
And alter your TaskRepository class:
class TaskRepository(private val userDao: UserDao, private val userId: Int) {
val allTasks: LiveData<List<Task>>
get() = userDao.getAllUsersTasks(userId)
val allUsers: LiveData<List<User>>
get() = userDao.getAllUsers()
...
}

Test a view model with livedata, coroutines (Kotlin)

I've been trying to test my view model for several days without success.
This is my view model :
class AdvertViewModel : ViewModel() {
private val parentJob = Job()
private val coroutineContext: CoroutineContext
get() = parentJob + Dispatchers.Default
private val scope = CoroutineScope(coroutineContext)
private val repository : AdvertRepository = AdvertRepository(ApiFactory.Apifactory.advertService)
val advertContactLiveData = MutableLiveData<String>()
fun fetchRequestContact(requestContact: RequestContact) {
scope.launch {
val advertContact = repository.requestContact(requestContact)
advertContactLiveData.postValue(advertContact)
}
}
}
This is my repository :
class AdvertRepository (private val api : AdvertService) : BaseRepository() {
suspend fun requestContact(requestContact: RequestContact) : String? {
val advertResponse = safeApiCall(
call = {api.requestContact(requestContact).await()},
errorMessage = "Error Request Contact"
)
return advertResponse
}
}
This is my view model test :
#RunWith(JUnit4::class)
class AdvertViewModelTest {
private val goodContact = RequestContact(...)
private lateinit var advertViewModel: AdvertViewModel
private var observer: Observer<String> = mock()
#get:Rule
var instantTaskExecutorRule = InstantTaskExecutorRule()
#Before
fun setUp() {
advertViewModel = AdvertViewModel()
advertViewModel.advertContactLiveData.observeForever(observer)
}
#Test
fun fetchRequestContact_goodResponse() {
advertViewModel.fetchRequestContact(goodContact)
val captor = ArgumentCaptor.forClass(String::class.java)
captor.run {
verify(observer, times(1)).onChanged(capture())
assertEquals("someValue", value)
}
}
}
The method mock() :
inline fun <reified T> mock(): T = Mockito.mock(T::class.java)
I got this error :
Wanted but not invoked: observer.onChanged();
-> at com.vizzit.AdvertViewModelTest.fetchRequestContact_goodResponse(AdvertViewModelTest.kt:52)
Actually, there were zero interactions with this mock.
I don't understand how to retrieve the result of my query.
You would need to write a OneTimeObserver to observe livedata from the ViewModel
class OneTimeObserver<T>(private val handler: (T) -> Unit) : Observer<T>, LifecycleOwner {
private val lifecycle = LifecycleRegistry(this)
init {
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
}
override fun getLifecycle(): Lifecycle = lifecycle
override fun onChanged(t: T) {
handler(t)
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
}
}
After that you can write an extension function:
fun <T> LiveData<T>.observeOnce(onChangeHandler: (T) -> Unit) {
val observer = OneTimeObserver(handler = onChangeHandler)
observe(observer, observer)
}
Than you can check this ViewModel class class that I have from a project to check what's going on with your LiveData after you act (when) with invoking a method.
As for your error, it just says that the onChanged() method is not being called ever.