Trying to fetch the data from the server using Retrofit and caching it offline using Room Database.
Problem I have fetched the data from the server and inserted it into the room but could not populate it. After evaluating the expression, the LiveData list is null.
I am certain that the insertion is successful, as I went through some answers on StackOverflow which mentioned to return the list of IDs to find out if there has been an insert.
Below is my code:
User.kt
#Entity(tableName = "user_table")
data class User(
#PrimaryKey(autoGenerate = true)
val id:Long,
#Json(name = "name")
val uname:String,
val username:String,
val email:String,
#Embedded
val address: Address,
val phone:String,
val website:String,
#Embedded
val company: Company)
data class Address(
val street:String,
val suite:String,
val city:String,
val zipcode:String,
#Embedded
val geo: Geo)
data class Geo(
val lat:String,
val lng:String)
data class Company(
#Json(name = "name")
val companyName:String,
val catchPhrase:String,
val bs:String)
UserDao.kt
#Dao
interface UserDao{
#Query("select * from user_table")
fun getUserList():LiveData<List<User>>
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertAll(userList:List<User>):List<Long>
}
UserRepository.kt
class UserRepository(private val userDao: UserDao) {
val userList: LiveData<List<User>> = userDao.getUserList()
suspend fun refreshUserList() {
withContext(Dispatchers.IO) {
val userList = UserApi.retrofitService.getUserData().await()
val list = userDao.insertAll(userList)
Log.e("list","${list.size}")
}
}
}
MainActivityViewModel.kt
class MainActivityViewModel(database:UserDao, application: Application) : AndroidViewModel(application){
private val userRepository = UserRepository(database)
val usersList = userRepository.userList
private var viewModelJob = Job()
private val coroutineScope = CoroutineScope(viewModelJob + Dispatchers.Main)
init {
getUsersList()
}
private fun getUsersList() {
coroutineScope.launch {
try{
userRepository.refreshUserList()
}catch (networkError: IOException){
Toast.makeText(getApplication(),"Failure",Toast.LENGTH_SHORT).show()
}
}
}
override fun onCleared() {
super.onCleared()
viewModelJob.cancel()
//Main View Model Destroys
}
}
UserAdapter.kt
class UserAdapter : RecyclerView.Adapter<UserAdapter.ViewHolder>(){
var userDetailsList:List<User> = emptyList()
set(value) {
field = value
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(UserCellLayoutBinding.inflate(
LayoutInflater.from(parent.context)
))
}
override fun getItemCount(): Int {
return userDetailsList.size
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val user = userDetailsList[position]
holder.bind(user)
}
class ViewHolder(private var binding: UserCellLayoutBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(user: User){
binding.user = user
binding.executePendingBindings()
}
}
}
MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var viewModelFactory: ViewModelFactory
private lateinit var viewModel: MainActivityViewModel
private var adapter:UserAdapter ?= null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
val dataSource = UserRoomDatabase.getInstance(application).userDao
viewModelFactory = ViewModelFactory(dataSource,application)
viewModel = ViewModelProviders.of(this,viewModelFactory).get(MainActivityViewModel::class.java)
binding.lifecycleOwner = this
binding.mainActivityViewModel = viewModel
binding.executePendingBindings()
binding.userListRecyclerView.adapter = adapter
viewModel.usersList.observe(this, Observer<List<User>> {users ->
users?.apply {
adapter?.userDetailsList = users
}
})
}
}
Any help would be greatly appreciated.
Thanks in advance.
I forgot to instantiate the adapter in MainActivity.kt
adapter = UserAdapter()
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
val dataSource = UserRoomDatabase.getInstance(application).userDao
viewModelFactory = ViewModelFactory(dataSource,application)
viewModel = ViewModelProviders.of(this,viewModelFactory).get(MainActivityViewModel::class.java)
binding.lifecycleOwner = this
binding.mainActivityViewModel = viewModel
binding.executePendingBindings()
adapter = UserAdapter()
binding.userListRecyclerView.adapter = adapter
viewModel.usersList.observe(this, Observer<List<User>> {users ->
users?.apply {
adapter?.userDetailsList = users
}
})
Related
It's first time using Room Data while also using MVVM pattern. The aim is that I want my data to appeard on the RecyclerList but it's doesn't shut down nor shows me any error it's just appears empty.
Here is my Database class:
#Database(entities = [Plant::class, Plant_Category::class], version = 1)
abstract class PlantDatabase:RoomDatabase() {
abstract fun plantDao(): PlantOperations
abstract fun plantCategoryDao(): PlantCategoryOperations
companion object {
private var INSTANCE: PlantDatabase? = null
fun getDatabase(context: Context): PlantDatabase {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(
context.applicationContext,
PlantDatabase::class.java, DB_NAME // contains directory of sqlite database
)
.fallbackToDestructiveMigration()
.build()
}
return INSTANCE!!
}
}
}
My dao class:
#Dao
interface PlantOperations {
#Query("SELECT * FROM Plant")
fun getAll(): Flow<List<Plant>>
#Insert
fun insertPlant( plant: Plant)
#Delete
fun delete(plant:Plant)
#Update
fun updatePlant(plant:Plant)}
This is my repository class:
class PlantRepository(application:Application){
private var allPlants = MutableLiveData<List<Plant>>()
private val plantDAO = PlantDatabase.getDatabase(application).plantDao()
init {
CoroutineScope(Dispatchers.IO).launch {
val plantData = plantDAO.getAll()
plantData.collect{
allPlants.postValue(it)
}
}
}
fun getAllPlants(): MutableLiveData<List<Plant>> {
return allPlants
}
}
My Viewmodel class:
class PlantViewModel(
application: Application
): AndroidViewModel(application) {
private var repository = PlantRepository(application)
private var _allPlants = repository.getAllPlants()
val allPlants: MutableLiveData<List<Plant>>
get() = _allPlants
}
My Recycler in Fragment:
override fun onCreateView(inflater: LayoutInflater,
container: ViewGroup?, savedInstanceState: Bundle?): View? {
lateinit var photoAdapter: Photo_Adapter
lateinit var plantViewModel: PlantViewModel
val view: View = inflater.inflate(R.layout.fragment_edit__form, container, false)
val fab = view.findViewById(R.id.floatingActionButton) as FloatingActionButton
val recyclerView = view.findViewById(R.id.recyclerView) as RecyclerView
recyclerView.layoutManager = GridLayoutManager(context, 2)
photoAdapter = Photo_Adapter(context)
recyclerView.adapter = photoAdapter
plantViewModel = ViewModelProvider(this).get(PlantViewModel::class.java)
plantViewModel.allPlants.observe(viewLifecycleOwner, androidx.lifecycle.Observer {
photoAdapter.setDataList(it)
})
// photoAdapter.setDataList(dataList)
//Floating button that opens the Form in order to add plant
fab?.setOnClickListener {
val intent = Intent(view.context, Edit_Form::class.java)
startActivity(intent);
}
return view
}
This is my adapter class:
class Photo_Adapter(var context: Context?) : RecyclerView.Adapter<Photo_Adapter.ViewHolder>() {
var dataList = emptyList<Plant>()
internal fun setDataList(dataList: List<Plant>) {
this.dataList = dataList
notifyDataSetChanged()
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
// Get the data model based on position
var data = dataList[position]
holder.title.text = data.name
holder.desc.text = data.type.toString()
holder.image.setImageResource(data.image)
holder.relativeLayout.setOnClickListener { view -> //Toast.makeText(view.getContext(),"click on item: "+model.getTitle(),Toast.LENGTH_LONG).show();
val intent = Intent(view.context, PlantDetails::class.java)
intent.putExtra("plant_name", data.name)
intent.putExtra("plant_image",data.image)
intent.putExtra("plant_type", data.type.type)
intent.putExtra("plant_water", data.type.water_time)
intent.putExtra("plant_details", data.type.details)
view.context.startActivity(intent)
}
}
// Provide a direct reference to each of the views with data items
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var image: ImageView
var title: TextView
var desc: TextView
var relativeLayout: CardView
init {
image = itemView.findViewById(R.id.image)
title = itemView.findViewById(R.id.title)
desc = itemView.findViewById(R.id.desc)
relativeLayout = itemView.findViewById<View>(R.id.relativeLayout) as CardView
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Photo_Adapter.ViewHolder {
// Inflate the custom layout
var view = LayoutInflater.from(parent.context).inflate(R.layout.photo_layout, parent, false)
return ViewHolder(view)
}
// total count of items in the list
override fun getItemCount() = dataList.size
}
Perhaps I forgot to add something? In Anyway I will be grateful for your help.
You should not observe data on your repository, your activity/view should observe this data. Take a look at this:
First, add this dependency to your gradle:
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1"
Then in your repository
class PlantRepository(application:Application){
private val plantDAO = PlantDatabase.getDatabase(application).plantDao()
fun getAllPlants(): Flow<List<Plant>> = plantDAO.getAll()
}
In your view model:
class PlantViewModel(
application: Application
): AndroidViewModel(application) {
private var repository = PlantRepository(application)
val allPlants = repository.getAllPlants()
.flowOn(Dispatchers.IO)
.asLiveData()
}
And your activity is fine, but check your adapter, make sure that you're notifing your adapter that your list has changed.
You can also work (collect) flows on your view, but this depends on you
I am not sure what is exactly happening but when ApiService.apiService.getPokemon(name) in fun getPokemon in PokemonRepository.kt is called then the function getPokemon stops executing and emited livedata are then observed as null in DetailAktivity.kt instead of a valid Pokemon class.
I have checked the API call and it is working in other cases. I am new to Android programming, so I would appreciate some detailed explanation.
Here are the classes:
PokemonRepository.kt
class PokemonRepository(context: Context) {
companion object {
private val TAG = PokemonRepository::class.java.simpleName
}
private val pekemonDao = PokemonDatabase.getInstance(context).pokemonDao()
fun getPokemon(name: String) = liveData {
val disposable = emitSource(
pekemonDao.getOne(name).map {
it
}
)
val pokemon = ApiService.apiService.getPokemon(name)
try {
disposable.dispose()
pekemonDao.insertAllPokemons(pokemon)
pekemonDao.getOne(name).map {
it
}
} catch (e: Exception) {
Log.e(TAG, "Getting data from the Internet failed", e)
pekemonDao.getOne(name).map {
e
}
}
}
DetailActivity.kt
class DetailActivity : AppCompatActivity() {
companion object {
const val ITEM = "item"
}
private lateinit var binding: ActivityDetailBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityDetailBinding.inflate(layoutInflater)
setContentView(binding.root)
val vm: DetailViewModel by viewModels()
vm.pokemon.observe(
this,
{
binding.name.text = it.name
supportActionBar?.apply {
setDisplayShowTitleEnabled(true)
title = it.name
}
}
)
intent.extras?.apply {
vm.setCharacterId(getString(ITEM)!!)
}
}
}
DetailViewModel
class DetailViewModel(application: Application) : AndroidViewModel(application) {
private val repository = PokemonRepository(application)
private val name: MutableLiveData<String> = MutableLiveData()
val pokemon = name.switchMap { name ->
repository.getPokemon(name)
}
fun setCharacterId(characterId: String) {
name.value = characterId
}
}
ApiService.kt
interface ApiService {
#GET("pokemon?offset=0&limit=151")
suspend fun getPokemons(#Query("page") page: Int): NamedApiResourceList
#GET("pokemon/{name}")
suspend fun getPokemon(#Path("name") name: String): Pokemon
companion object {
private const val API_ENDPOINT = "https://pokeapi.co/api/v2/"
val apiService by lazy { create() }
private fun create(): ApiService = Retrofit.Builder()
.baseUrl(API_ENDPOINT)
.addConverterFactory(MoshiConverterFactory.create())
.client(OkHttpClient())
.build()
.create(ApiService::class.java)
}
}
Pokemon data class
#Parcelize
#JsonClass(generateAdapter = true)
#Entity
data class Pokemon(
#PrimaryKey val id: Int,
val name: String,
#ColumnInfo(name = "base_experience") val baseExperience: Int,
val height: Int,
#ColumnInfo(name = "is_default") val isDefault: Boolean,
val order: Int,
val weight: Int,
val sprites: PokemonSprites,
) : Parcelable
PokemonDao.kt
#Dao
interface PokemonDao {
#Query("SELECT * FROM namedapiresource")
fun getAll(): LiveData<List<NamedApiResource>>
#Query("SELECT * FROM pokemon WHERE name=:name")
fun getOne(name: String): LiveData<Pokemon>
#Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insertAllNamedApiResources(vararg characters: NamedApiResource)
#Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insertAllPokemons(vararg characters: Pokemon)
}
I would guess because of how your getPokemon is defined:
val disposable = emitSource(
// Attempts to get a pokemon from the database - presumably this does
// not exist at first so would return null first
pekemonDao.getOne(name).map {
it
}
)
// AFTER NULL IS RETURNED this tries to fetch from the API
val pokemon = ApiService.apiService.getPokemon(name)
try {
disposable.dispose()
pekemonDao.insertAllPokemons(pokemon)
// After fetching from API, finally returns a non-null
pekemonDao.getOne(name).map {
it
}
So maybe just get ride of the initial block?
val disposable = emitSource(
pekemonDao.getOne(name).map {
it
}
)
I have two activity and each activity has recyclerview, I have nested json.I want when user tap first activity recyclerview then they goes to next activity and show the recyclerview.Better understanding please follow the json link and code.
Json Link
: https://run.mocky.io/v3/8c3f6be2-a53f-47da-838c-72e603af844d
CropData.kt
data class CropData(
#SerializedName("cropImage")
val cropImage: String,
#SerializedName("cropName")
val cropName: String,
#SerializedName("cropTime")
val cropTime: String,
#SerializedName("cropDetails")
val cropDetails: String,
#SerializedName("cropProcess")
val cropProcess: String,
#SerializedName("cropType")
val cropType: String,
#SerializedName("cropFertilizer")
val cropFertilizer: List<CropFertilizer>
)
CropFertilizer.kt
data class CropFertilizer(
#SerializedName("fertilizerName")
val fertilizerName: String,
#SerializedName("fertilizerFirst")
val fertilizerFirst: String,
#SerializedName("fertilizerSecond")
val fertilizerSecond: String,
#SerializedName("fertilizerThird")
val fertilizerThird: String
)
CropActivity.kt
class CropActivity : AppCompatActivity(), CropOnItemClickListener {
private lateinit var viewModel: CropActivityViewModel
private val cropAdapter by lazy { CropAdapter(this) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_crop)
val repository = Repository()
val viewModelFactory = CropActivityViewModelFactory(repository)
viewModel = ViewModelProvider(this, viewModelFactory).get(CropActivityViewModel::class.java)
viewModel.getCropData()
viewModel.cropResponse.observe(this, Observer { response ->
cropAdapter.setData(response)
Log.d("dCrop", response.toString())
})
setUpCrops()
}
private fun setUpCrops() {
crop_recyclerview.layoutManager = LinearLayoutManager(
this,
LinearLayoutManager.VERTICAL, false
)
crop_recyclerview.setHasFixedSize(true)
crop_recyclerview.adapter = cropAdapter
}
override fun onClick(item: CropData, position: Int) {
val intent = Intent(this, CropDetailsActivity::class.java)
intent.putExtra("name", item.cropName)
intent.putExtra("image", item.cropImage)
intent.putExtra("intro", item.cropDetails)
intent.putExtra("process", item.cropProcess)
intent.putExtra("type", item.cropType)
intent.putExtra("time", item.cropTime)
startActivity(intent)
}
}
CropAdapter.kt
class CropAdapter(private val cropOnItemClickListener: CropOnItemClickListener) :
RecyclerView.Adapter<CropAdapter.ViewHolder>() {
private var cropList = emptyList<CropData>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.crop_row,parent,false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.itemView.crop_name.text = cropList[position].cropName
holder.itemView.crop_details.text = cropList[position].cropDetails
holder.itemView.crop_time.text = cropList[position].cropTime
holder.itemView.setOnClickListener{
cropOnItemClickListener.onClick(cropList[position],position)
}
}
override fun getItemCount(): Int {
return cropList.size
}
fun setData(newList: List<CropData>){
notifyDataSetChanged()
cropList = newList
notifyDataSetChanged()
}
class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView)
}
CropDetailsActivity.kt
class CropDetailsActivity : AppCompatActivity() {
private lateinit var viewModel: CropDetailsActivityViewModel
private val fertlizerAdapter by lazy { FertilizerAdapter() }
private val cropFertilizerAdapter by lazy { FertilizerAdapter() }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_crop_details)
val bundle:Bundle? = intent.extras
crop_details_name.text = bundle!!.getString("name")
val i: String = bundle!!.getString("intro")
crop_details_intro.text = i
Glide.with(this).load(bundle.getString("image")).into(crop_details_image);
val j: String = bundle!!.getString("process")
crop_details_process.text = j
crop_details_time.text = bundle!!.getString("time")
crop_details_type.text = bundle!!.getString("type")
val repository = Repository()
val viewModelFactory = CropDetailsActivityViewModelFactory(repository)
viewModel = ViewModelProvider(this, viewModelFactory).get(CropDetailsActivityViewModel::class.java)
viewModel.getFertilizer()
viewModel.fResponse.observe(this, Observer {
cropFertilizerAdapter.setData(it)
})
crop_details_recyclerview.layoutManager = LinearLayoutManager(
this,
LinearLayoutManager.VERTICAL, false
)
crop_details_recyclerview.setHasFixedSize(true)
crop_details_recyclerview.adapter = cropFertilizerAdapter
}
}
Better understanding see the image
[1]: https://i.stack.imgur.com/BtPqe.jpg
I want to show CropFertilizer list in CropDetailsActivity.How can i do this?
I get aMVoice from DetailViewModel and present it in UI for edit.
I invoke isChanged() in FragmentDetail and check whether the content of aMVoice is changed.
I can't get the correct result, I find that val b=(mDBVoiceRepository.getVoiceById(voiceId)).value always return null, why? How can I fix it ?
Code
class FragmentDetail : Fragment() {
private val mDetailViewModel by lazy {
getViewModel {
DetailViewModel( provideRepository(mContext), args.voiceId)
}
}
fun isChanged():Boolean{
mDetailViewModel.checkChanged()
return mDetailViewModel.isChanged.value!!
}
}
class DetailViewModel(private val mDBVoiceRepository: DBVoiceRepository, private val voiceId:Int) : ViewModel() {
val aMVoice=mDBVoiceRepository.getVoiceById(voiceId)
private val _isChanged= MutableLiveData<Boolean>(false)
val isChanged: LiveData<Boolean> = _isChanged
fun checkChanged(){
val a=aMVoice.value
val b=(mDBVoiceRepository.getVoiceById(voiceId)).value //It's null
_isChanged.value=!(a==b)
}
}
class DBVoiceRepository private constructor(private val mDBVoiceDao: DBVoiceDao){
fun getVoiceById(id:Int)=mDBVoiceDao.getVoiceById(id)
}
interface DBVoiceDao{
#Query("SELECT * FROM voice_table where id=:id")
fun getVoiceById(id:Int):LiveData<MVoice>
}
#Entity(tableName = "voice_table", indices = [Index("createdDate")])
data class MVoice(
#PrimaryKey (autoGenerate = true) #ColumnInfo(name = "id") var id: Int = 0,
var name: String = "",
var path: String = ""
)
It appears to me that you are using live data without observing it, live data does not emit ant data except it has an active observer. So you should create an observer in your fragment instead of manually checking for the value.
Also, it's not necessary to keep variables to detect if the value has changed, live data can do that for you with distinctUntilChanged() operator.
So your code should look like something below
class FragmentDetail : Fragment() {
private val mDetailViewModel by lazy {
getViewModel {
DetailViewModel(provideRepository(mContext), args.voiceId)
}
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
observeVoiceChange()
}
fun observeVoiceChange() {
mDetailViewModel.checkChanged().distinctUntilChanged().observe(viewLifecycleOwner, Observer<MVoice> {
//Do whatever you want when voice changes
})
}
}
class DetailViewModel(private val mDBVoiceRepository: DBVoiceRepository, private val voiceId: Int) : ViewModel() {
fun checkChanged(): LiveData<MVoice> {
return (mDBVoiceRepository.getVoiceById(voiceId))
}
}
class DBVoiceRepository private constructor(private val mDBVoiceDao: DBVoiceDao) {
fun getVoiceById(id: Int) = mDBVoiceDao.getVoiceById(id)
}
interface DBVoiceDao {
#Query("SELECT * FROM voice_table where id=:id")
fun getVoiceById(id: Int): LiveData<MVoice>
}
#Entity(tableName = "voice_table", indices = [Index("createdDate")])
data class MVoice(
#PrimaryKey(autoGenerate = true) #ColumnInfo(name = "id") var id: Int = 0,
var name: String = "",
var path: String = ""
)
once the fragment starts, it should observe for changes in the voice data and return and work with those changes.
I'm trying to fetch edittext value from one activity and displaying that text in recycleview and storing using room DB....
Basically the idea is adding the address in activity address when clicks on plus it will redirect to next page where the user gets the address form onsubmit it will fetch address and add to previous activity recycleview.
here is my code for room:
table:---
#Entity(tableName = "address")
class Address {
#PrimaryKey
var id = 0
#ColumnInfo(name = "address")
var address: String? = null
}
Database:-
#Database(entities = [Address::class], version = 1)
abstract class Database : RoomDatabase() {
abstract fun AddressDao(): AddressDao
}
AddressDao:
#Dao
interface AddressDao {
#Insert
suspend fun addData(address: Address)
#Query("select * from address")
fun getAddressesWithChanges() :LiveData<List<Address>>
#Query("SELECT EXISTS (SELECT 1 FROM address WHERE id=:id)")
suspend fun isAddressAdded(id: Int): Int
#Delete
suspend fun delete(address: Address)
}
AddAddressActivity activity:
class AddAddressActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.addaddress)
val editText: EditText = findViewById(R.id.longaddress)
val application = application as CustomApplication
saveaddress.setOnClickListener {
val address = Address()
address.address = editText.getText().toString()
lifecycleScope.launch {
application.addressDao.addData(address)
finish()
}
}
}
}
AddressActivity:-
class AddressActivity : AppCompatActivity() {
private val adapter = AddressAdapter()
private lateinit var data: LiveData<List<Address>>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.address)
addbutton.findViewById<View>(R.id.addbutton).setOnClickListener {
val intent = Intent(this, AddAddressActivity::class.java)
startActivity(intent)
}
val recyclerView = findViewById<RecyclerView>(R.id.recyclerview)
recyclerView.setHasFixedSize(true)
recyclerView.layoutManager = LinearLayoutManager(this,
LinearLayoutManager.VERTICAL, false)
recyclerView.adapter = adapter
val application = application as CustomApplication
data = application.database.AddressDao().getAddressesWithChanges()
data.observe(this, Observer { words1 ->
// Update the cached copy of the words in the adapter.
words1?.let { adapter.updateData(it) }})
}
}
AddressAdapter activity:-
class AddressAdapter: RecyclerView.Adapter<AddressAdapter.ViewHolder>() {
private var addresses: List<Address> = Collections.emptyList()
override fun onCreateViewHolder(viewGroup: ViewGroup, itemViewType: Int):
ViewHolder =
ViewHolder(LayoutInflater.from(viewGroup.context)
.inflate(R.layout.address_item, viewGroup, false) )
override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
val fl: Address = addresses[position]
viewHolder.tv.setText(fl.address)
}
override fun getItemCount(): Int = addresses.size
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var tv: TextView
init {
tv = itemView.findViewById(R.id.ftv_name)
} }
fun updateData(addresses:
List<Address>) {
this.addresses = addresses
notifyDataSetChanged() // TODO: use ListAdapter if animations are needed
}
}
CustomApplication:
class CustomApplication: Application() {
lateinit var database: Database
private set
lateinit var addressDao: AddressDao
private set
override fun onCreate() {
super.onCreate()
this.database = Room.databaseBuilder<Database>(
applicationContext,
Database::class.java, "database"
).build()
}
}
list is not displaying recycleview is empty when came back to address activity
please help ..I'm new to kotlin and room db..
abstract fun AddressDao(): AddressDao?
Why is this nullable?
val favoriteData: List<Table>?
Why is this nullable?
fun addData(favoriteList: Table?)
Why is this nullable?
fun delete(favoriteList: Table?)
Why is this nullable?
Typed nullability is to enable you to represent where a given value can be null, in the sense that it makes sense for it to be absent.
Neither of the above cases represent such a scenario. Room will never return you null instead of an actual dao.
Kotlin's null safety is to allow you to eliminate potential error cases by making it part of the type system, it shouldn't be used to create "false" error cases such as these. If you're trying to add null to a database instead of a real object, you've probably got a bug in your code, and Kotlin should be able to help you eliminate that possibility.
As for your actual question, you're misusing Room.
.allowMainThreadQueries() is a way to disable an error check, but that error is there for a reason. You're not supposed to be reading the data on the UI thread, because with enough data, your app will freeze (ANR).
The code is supposed to look something like this:
#Entity(tableName = "address")
class Address {
#PrimaryKey
var id = 0
#ColumnInfo(name = "address")
var address: String? = null
}
Database:-
#Database(entities = [Address::class], version = 1)
abstract class Database : RoomDatabase() {
abstract fun addressDao(): AddressDao
}
AddressDao:
#Dao
interface AddressDao {
#Insert
suspend fun addData(address: Address)
#Query("select * from address")
fun getAddressesWithChanges(): LiveData<List<Address>>
#Query("SELECT EXISTS (SELECT 1 FROM address WHERE id=:id)")
suspend fun isAddressAdded(id: Int): Int
#Delete
suspend fun delete(address: Address)
}
class CustomApplication: Application() {
lateinit var database: Database
private set
lateinit var addressDao: AddressDao
private set
override fun onCreate() {
super.onCreate()
this.database = Room.databaseBuilder<Database>(
applicationContext,
Database::class.java, "database"
).build()
addressDao = database.addressDao()
}
}
AndroidManifest.xml
<application android:name=".CustomApplication"
...
Address activity:
import androidx.lifecycle.observe
class AddressActivity : AppCompatActivity() {
private val adapter = AddressAdapter()
private lateinit var data: LiveData<List<Address>>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.address_activity)
findViewById<View>(R.id.addButton).setOnClickListener {
val intent = Intent(this, AddAddressActivity::class.java)
startActivity(intent)
}
val recyclerView = findViewById<RecyclerView>(R.id.recyclerview)
recyclerView.setHasFixedSize(true)
recyclerView.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
recyclerView.adapter = adapter
val application = application as CustomApplication
val addressDao = application.addressDao
data = addressDao.getAddressesWithChanges()
data.observe(this) {
adapter.updateData(it)
}
}
}
AddressAdapter:
class AddressAdapter: RecyclerView.Adapter<AddressAdapter.ViewHolder>() {
private var addresses: List<Table> = Collections.emptyList()
override fun onCreateViewHolder(viewGroup: ViewGroup, itemViewType: Int): ViewHolder =
ViewHolder(LayoutInflater.from(viewGroup.context).inflate(R.layout.address_item, viewGroup, false))
override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
val address = addresses[position]
viewHolder.tv.setText(address.address)
}
override fun getItemCount(): Int = addresses.size
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val tv = itemView.findViewById(R.id.ftv_name)
}
fun updateData(addresses: List<Address>) {
this.addresses = addresses
notifyDataSetChanged() // TODO: use ListAdapter if animations are needed
}
}
AddAddress activity:-
class AddAddressActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.addaddress)
val editText: EditText = findViewById(R.id.longaddress)
val application = application as CustomApplication
val addressDao = application.addressDao
saveaddress.setOnClickListener {
val address = Address()
address.address = editText.getText().toString()
lifecycleScope.launch {
addressDao.addData(address)
finish()
}
}
}
}
You can potentially also refer to https://codelabs.developers.google.com/codelabs/android-room-with-a-view-kotlin/#6
Please replace your line with
val rv=findViewById<RecyclerView>(R.id.recyclerview)
You have passed the wrong id addressrecyclerview that's why it was assigning null