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

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()
...
}

Related

Why is my data size zero from repository in 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)

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
}
}

Retrieve data from api and put into room

I'm facing this error when putting data into room from my api:
java.lang.RuntimeException: Unable to invoke no-args constructor for
retrofit2.Call<com.example.youbank.models.Customer>. Registering an
InstanceCreator with Gson for this type may fix this problem.
I have looked up this issue and tried multiple things to fix it but, i think there is something else wrong, which probably comes down to my lack of knowledge on this subject.
Sorry for the big copypaste of my code but i don't know where to fix this problem so im just including what i think is needed.
HomeScreenFragment:
class HomeScreenMotionFragment: Fragment(), CoroutineScope {
private var job: Job = Job()
override val coroutineContext: CoroutineContext
get() = Dispatchers.IO + job
override fun onDestroy() {
super.onDestroy()
job.cancel()
}
private var _binding: FragmentHomeScreenMotionBinding? = null
private val binding get() = _binding!!
private val vm: CustomerViewModel by activityViewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
launch {
vm.addCustomerToRoomDB(14)
}
}
CustomerViewModel:
class CustomerViewModel(application: Application): AndroidViewModel(application) {
val readCustomer: LiveData<RoomCustomer>
val readAccount: LiveData<List<RoomAccount>>
val readCard: LiveData<List<RoomCard>>
private val customerRepo: CustomerRepository
private val accountRepo: AccountRepository
private val cardRepo: CardRepository
var cus: Customer
var a = listOf<Account>()
var cards = listOf<Card>()
init {
cus = Customer()
val customerDao = CustomerDatabase.getDatabase(application).customerDao()
customerRepo = CustomerRepository(customerDao)
readCustomer = customerRepo.readCustomer
val accountDao = CustomerDatabase.getDatabase(application).accountDao()
accountRepo = AccountRepository(accountDao)
readAccount = accountRepo.readAccounts
val cardDao = CustomerDatabase.getDatabase(application).cardDao()
cardRepo = CardRepository(cardDao)
readCard = cardRepo.readCards
}
suspend fun addCustomerToRoomDB(id: Int) {
val service: CustomerService = ApiService.buildService(CustomerService::class.java)
val req: Call<Customer> = service.getCustomerById(id)
req.enqueue(object: Callback<Customer> {
override fun onResponse(call: Call<Customer>, response: Response<Customer>) {
cus = response.body()!!
a = response.body()!!.accounts
cards = response.body()!!.accounts[0].cards
}
override fun onFailure(call: Call<Customer>, t: Throwable) {
Log.d("get customer failed", t.cause.toString())
}
})
val roomCustomer = RoomCustomer(
0, cus.customerId, cus.fullName, cus.phone, cus.address, cus.birthday.toString(), cus.email, cus.password)
val roomAccount = RoomAccount(0, a[0].accountId, a[0].accountNumber, a[0].accountType, a[0].balance)
val roomCard = RoomCard(
0, cards[0].cardId, cards[0].cardNumber, cards[0].ccv, cards[0].expirationDate, cards[0].cardType,
cards[0].cardStatus)
// Adding customer to roomdatabase
customerRepo.addCustomer(roomCustomer)
accountRepo.addAccounts(roomAccount)
cardRepo.addCards(roomCard)
}
}
CustomerService:
interface CustomerService {
#GET("Customers/{id}")
suspend fun getCustomerById(#Path("id") id: Int): Call<Customer>
}
My models:
class Customer {
var customerId: Int = -1
var fullName: String = ""
var phone: String = ""
var address: String = ""
var birthday: Date? = null
var email: String = ""
var password: String = ""
lateinit var accounts: List<Account>
}
class Account {
var accountId: Int = -1
var accountNumber: String = generateAccNumber()
lateinit var accountType: AccountType
var balance: Double = 0.0
lateinit var cards: List<Card>
}
class Card {
var cardId: Int = -1
var cardNumber: Int = -1
var ccv: Int = -1
lateinit var expirationDate: String
lateinit var cardType: CardType
lateinit var cardStatus: CardStatus
}
My repositories:
class CustomerRepository(private val customerDao: CustomerDao) {
val readCustomer: LiveData<RoomCustomer> = customerDao.getCustomer()
fun addCustomer(c: RoomCustomer) {
customerDao.addCustomer(c)
}
}
class AccountRepository (private val accountDao: AccountDao) {
val readAccounts: LiveData<List<RoomAccount>> = accountDao.getAccounts()
fun addAccounts(a: RoomAccount) {
accountDao.addAccount(a)
}
}
class CardRepository(private val cardDao: CardDao) {
val readCards: LiveData<List<RoomCard>> = cardDao.getCards()
fun addCards(c: RoomCard) {
cardDao.addCards(c)
}
}
My daos:
#Dao
interface CustomerDao {
#Query("SELECT * FROM customer_table")
fun getCustomer(): LiveData<RoomCustomer>
#Insert
fun addCustomer(c: RoomCustomer)
#Delete
fun deleteCustomer(c: RoomCustomer)
}
#Dao
interface AccountDao {
#Query("SELECT * FROM accounts_table")
fun getAccounts(): LiveData<List<RoomAccount>>
#Insert
fun addAccount(a: RoomAccount)
#Delete
fun deleteAccount(a: RoomAccount)
}
#Dao
interface CardDao {
#Query("SELECT * FROM cards_table")
fun getCards(): LiveData<List<RoomCard>>
#Insert
fun addCards(c: RoomCard)
#Delete
fun deleteCard(c: RoomCard)
}
My room models:
#Entity(tableName = "customer_table")
data class RoomCustomer(
#PrimaryKey(autoGenerate = true)
val CID: Int,
val customerId: Int,
val fullName: String,
val phone: String,
val address: String,
val birthday: String,
val email: String,
val password: String
)
#Entity(tableName = "accounts_table")
data class RoomAccount(
#PrimaryKey(autoGenerate = true)
val AID: Int,
val accountId: Int,
val accountNumber: String,
val accountType: AccountType,
val balance: Double
)
#Entity(tableName = "cards_table")
data class RoomCard(
#PrimaryKey(autoGenerate = true)
val CID: Int,
val cardId: Int,
val cardNumber: Int,
val ccv: Int,
val expirationDate: String,
val cardType: CardType,
val cardStatus: CardStatus
)

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()
}
}
}

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.