App crashing while creating Dao object for room database - android

My app is crashing and the only thing I know for sure is that the main problem is this line ->
val placesDao = (application as HotspotBucketApp).placesDb.placesDao()
can any one tell please me why is this happening.
private fun getPlaceList() {
val placesDao = (application as HotspotBucketApp).placesDb.placesDao()
lifecycleScope.launch {
placesDao.fetchAllPlaces().collect {
placeList = ArrayList(it)
}
if (placeList.isNotEmpty()) {
lastId = placeList[placeList.size - 1].id
}
}
}
class HotspotBucketApp: Application() {
val placesDb by lazy {
PlacesDatabase.getInstance(this)
}
}
#Dao
interface PlacesDao {
#Query("select * from `placesTable`")
fun fetchAllPlaces(): Flow<List<PlaceEntity>>
#Insert
suspend fun insert(placeEntity: PlaceEntity)
#Delete
suspend fun delete(placeEntity: PlaceEntity)
}
#Database(entities = [PlaceEntity::class], version = 1)
abstract class PlacesDatabase: RoomDatabase() {
abstract fun placesDao(): PlacesDao
companion object {
#Volatile
private var INSTANCE: PlacesDatabase? = null
private const val PLACES_DB_NAME = "place_database"
fun getInstance(context: Context): PlacesDatabase {
synchronized(this) {
var instance = INSTANCE
if (instance == null) {
instance = Room.databaseBuilder(context.applicationContext, PlacesDatabase::class.java, PLACES_DB_NAME)
.fallbackToDestructiveMigration()
.build()
INSTANCE = instance
}
return instance
}
}
}
}
#Entity(tableName = "placesTable")
data class PlaceEntity(
#PrimaryKey
val id: Int,
val title: String,
val image: String,
val description: String,
val date: String,
val location: String,
val latitude: Double,
val longitude: Double
)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding?.root)
setSupportActionBar(binding?.addPlacesToolBar)
if (Build.VERSION.SDK_INT >= 33) {
onBackInvokedDispatcher.registerOnBackInvokedCallback(
OnBackInvokedDispatcher.PRIORITY_DEFAULT
) {
finish()
}
} else {
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
finish()
}
})
}
getPlaceList()
binding?.fABAddPlaces?.setOnClickListener {
val intent = Intent(this, AddPlaceActivity::class.java)
intent.putExtra("lats_place_id", lastId)
startActivity(intent)
}
}

Related

why returned flow in room is collecting for each of my SELECT after any changes in database(INSERT,UPDATE,DELETE) MVVM

I am using flow to get data after any changes in my room database ,my app is note and it use SELECT by searching and filter by priority [High,Normal,Low]. and it has delete and update and insert too. I get flows from model and in viewModel by search and filter I Collect my data from flows and post it to my single liveData and in the last I get them in my Observer in the view
my problem is after in SELECT when I use UPDATE or INSERTT or DELETE , all of my old SELECT(search and filter) repeat in the moment. it means if I search 1000 once, and use filter to my notes 99 once; I will collect 1099 flows in the moment again and my observer in the my view will be bombing!!!
My Dao:
#Dao
interface NoteDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun saveNote(entity : NoteEntity)
#Delete
suspend fun deleteNote(note : NoteEntity)
#Update
suspend fun updateNote(note : NoteEntity)
#Query("SELECT * FROM $NOTE_TABLE")
fun getAllNotes() : Flow<MutableList<NoteEntity>>
#Query("SELECT * FROM $NOTE_TABLE WHERE id == :id")
fun getNote(id : Int) : Flow<NoteEntity>
#Query("SELECT * FROM $NOTE_TABLE WHERE priority == :priority")
fun fileNote(priority : String) : Flow<MutableList<NoteEntity>>
#Query("SELECT * FROM $NOTE_TABLE WHERE title LIKE '%' || :title || '%'")
fun searchNote(title : String) : Flow<MutableList<NoteEntity>>
}
My Repository:
class MainRepository #Inject constructor(private val dao : NoteDao) {
fun getAllNotes() = dao.getAllNotes()
fun getNotesByPriority(priority : String) = dao.fileNote(priority)
fun getNotesBySearch(title : String) = dao.searchNote(title)
suspend fun deleteNote(note: NoteEntity) = dao.deleteNote(note)
}
My ViewModel:
#HiltViewModel
class MainViewModel #Inject constructor(private val repository: MainRepository) : ViewModel() {
val notesData = object : MutableLiveData<DataStatus<MutableList<NoteEntity>>>(){
override fun postValue(value: DataStatus<MutableList<NoteEntity>>?) {
super.postValue(value)
Log.e("TAGJH", "postValue: ${value?.data?.size}")
}
}
fun getAllNotes() = viewModelScope.launch {
repository.getAllNotes().collect {
Log.e("TAGJH", "getAll: ${it.size}")
notesData.postValue(DataStatus.success(it, it.isEmpty()))
}
}
fun getNoteByPriority(priority: String) = viewModelScope.launch {
repository.getNotesByPriority(priority).collect {
Log.e("TAGJH", "priority ${it.size }->$priority")
notesData.postValue(DataStatus.success(it, it.isEmpty()))
}
}
fun getNoteBySearch(characters: String) = viewModelScope.launch {
repository.getNotesBySearch(characters).collect {
Log.e("TAGJH", "collect: ${it.size}")
notesData.postValue(DataStatus.success(it, it.isEmpty()))
}
}
fun deleteNote(note: NoteEntity) = viewModelScope.launch {
repository.deleteNote(note)
}
}
My View:
#AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private var selectedItem: Int = 0
private var _binding: ActivityMainBinding? = null
private val binding get() = _binding
#Inject
lateinit var noteAdapter: NoteAdapter
#Inject
lateinit var noteEntity: NoteEntity
private val viewModel: MainViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
_binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding?.root)
binding?.apply {
setSupportActionBar(notesToolbar)
addNoteBtn.setOnClickListener {
NoteFragment().show(supportFragmentManager, NoteFragment().tag)
}
viewModel.notesData.observe(this#MainActivity) {
showEmpty(it.isEmpty)
noteAdapter.setData(it.data!!)
noteList.apply {
layoutManager =
StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)
adapter = noteAdapter
}
}
viewModel.getAllNotes()
notesToolbar.setOnMenuItemClickListener {
when (it.itemId) {
R.id.actionFilter -> {
filterByProperty()
return#setOnMenuItemClickListener true
}
else -> return#setOnMenuItemClickListener false
}
}
deleteAndUpdateListener()
}
}
private fun deleteAndUpdateListener() =
noteAdapter.onItemClickListener { note, title ->
when (title) {
DELETE -> {
noteEntity.id = note.id
noteEntity.title = note.title
noteEntity.des = note.des
noteEntity.priority = note.priority
noteEntity.category = note.category
viewModel.deleteNote(noteEntity)
}
EDIT -> {
val noteFragment = NoteFragment()
val bundle = Bundle()
bundle.putInt(BUNDLE_ID, note.id)
noteFragment.arguments = bundle
noteFragment.show(supportFragmentManager, NoteFragment().tag)
}
}
}
private fun showEmpty(isShown: Boolean) {
binding?.apply {
if (isShown) {
emptyShowing.visibility = View.VISIBLE
noteList.visibility = View.GONE
} else {
emptyShowing.visibility = View.GONE
noteList.visibility = View.VISIBLE
}
}
}
override fun onDestroy() {
super.onDestroy()
_binding = null
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_toolbar, menu)
val search = menu.findItem(R.id.actionSearch)
val searchView = search.actionView as SearchView
searchView.queryHint = getString(R.string.search)
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String?): Boolean {
return false
}
override fun onQueryTextChange(newText: String): Boolean {
viewModel.getNoteBySearch(newText)
Log.e("TAGJH", "searching")
return true
}
})
return super.onCreateOptionsMenu(menu)
}
private fun filterByProperty() {
val builder = AlertDialog.Builder(this)
val priories = arrayOf(ALL, HIGH, NORMAL, LOW)
builder.setSingleChoiceItems(priories, selectedItem) { dialog, item ->
when (item) {
0 -> {
viewModel.getAllNotes()
}
in 1..3 -> {
viewModel.getNoteByPriority(priories[item])
}
}
selectedItem = item
dialog.dismiss()
}
val dialog: AlertDialog = builder.create()
dialog.show()
}
}
I want to see my note after any update or delete or insert is updating in my last SELECT from room database

Data is not being displayed

So what I am trying to do here is to fetch data from the api by thecocktaildb.com, but I don't get any data shown.
Adapter:
`
class CocktailAdapter(private val cocktails: List<CocktailDetails>) :
RecyclerView.Adapter<CocktailAdapter.CocktailViewHolder>() {
class CocktailViewHolder(val binding: CocktailListItemBinding) :
RecyclerView.ViewHolder(binding.root)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CocktailViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = CocktailListItemBinding.inflate(layoutInflater, parent, false)
return CocktailViewHolder(binding)
}
override fun onBindViewHolder(holder: CocktailViewHolder, position: Int) {
val currentCocktail = cocktails[position]
holder.binding.apply {
cocktail = currentCocktail.strDrink
ivLiked.visibility = if (currentCocktail.favourite) View.VISIBLE else View.GONE
Glide
.with(root.context)
.load(currentCocktail.strDrinkThumb)
.centerCrop()
.placeholder(R.drawable.ic_launcher_foreground)
.into(ivImage)
holder.binding.root.setOnClickListener {
(it.context as MainActivity).supportFragmentManager.commit {
val bundle = Bundle()
bundle.putString("cocktail_name", currentCocktail.strDrink)
replace(R.id.container_view, CocktailDetailsFragment::class.java, bundle)
addToBackStack("cocktails_list_to_details")
}
}
}
}
override fun getItemCount(): Int {
return cocktails.size
}
}
`
Dao:
`
#Dao
interface CocktailDao {
#Query("SELECT * FROM cocktails")
suspend fun getCocktails(): List<CocktailDetails>
#Query("SELECT * FROM cocktails WHERE strDrink=:name")
suspend fun getCocktailByName(name: String): CocktailDetails
#Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insertAll(cocktails: List<CocktailDetails>)
#Update(onConflict = OnConflictStrategy.REPLACE)
suspend fun update(cocktail: CocktailDetails)
}
`
Entity:
`
#Entity(tableName = "cocktails")
data class CocktailDetails(
#PrimaryKey
#ColumnInfo(name = "strDrink") var strDrink: String,
#ColumnInfo(name = "strGlass") var strGlass: String,
#ColumnInfo(name = "strDrinkThumb") var strDrinkThumb: String,
// #ColumnInfo(name = "strIngredient") var strIngredient: String,
// #ColumnInfo(name = "strMeasure") var strMeasure: String,
#ColumnInfo(name = "strInstructions") var strInstructions: String,
#ColumnInfo(name = "favourite") var favourite: Boolean
)
`
AppDatabase:
`
package com.aleks.exam.db
import androidx.room.Database
import androidx.room.RoomDatabase
import com.aleks.exam.db.dao.CocktailDao
import com.aleks.exam.db.entity.CocktailDetails
#Database(entities = [CocktailDetails::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun cocktailDao(): CocktailDao
}
`
ViewModelFactory:
`
class CocktailViewModelFactory(
private val cocktailRepository: CocktailRepository
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return CocktailViewModel(cocktailRepository) as T
}
}
`
Fragment:
`
class CocktailDetailsFragment : Fragment() {
private lateinit var binding: FragmentCocktailDetailsBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val selectedCocktailName = arguments?.getString("cocktail_name", null)
GlobalScope.launch {
(activity as? MainActivity)?.cocktailViewModel?.getCocktailByName(
selectedCocktailName ?: return#launch
)
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentCocktailDetailsBinding.inflate(LayoutInflater.from(context))
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
observeData()
}
private fun observeData() {
GlobalScope.launch {
(activity as? MainActivity)?.cocktailViewModel?.selectedCocktail?.collect {
binding.cocktail = it ?: return#collect
(activity as? MainActivity)?.runOnUiThread {
setDataBinding()
Glide
.with(context ?: return#runOnUiThread)
.load(it.strDrinkThumb)
.centerCrop()
.placeholder(R.drawable.ic_launcher_foreground)
.into(binding.ivImage)
}
}
}
}
private fun setDataBinding() {
binding.cocktail ?: return
if (binding.cocktail?.favourite == true) {
binding.btnLike.setImageResource(android.R.drawable.star_big_on)
} else {
binding.btnLike.setImageResource(android.R.drawable.star_big_off)
}
binding.btnLike.setOnClickListener {
val cocktail = binding.cocktail
cocktail?.favourite = cocktail?.favourite != true
if (cocktail?.favourite == true) {
binding.btnLike.setImageResource(android.R.drawable.star_big_on)
} else {
binding.btnLike.setImageResource(android.R.drawable.star_big_off)
}
GlobalScope.launch {
(activity as? MainActivity)?.cocktailViewModel?.updateFavourites(
cocktail ?: return#launch
)
}
}
}
}
`
Model:
`
data class CocktailDetailResponse (
var strDrinkThumb: String,
var strDrink: String?,
var strGlass: String?,
// var strIngredient:Ingredient,
// var strMeasure:Measure,
var strInstructions:String?
)
//data class Ingredient (
// var strIngredient1:String,
// var strIngredient2:String,
// var strIngredient3:String,
// var strIngredient4:String,
// var strIngredient5:String,
// var strIngredient6:String,
// var strIngredient7:String,
// var strIngredient8:String,
// var strIngredient9:String,
// var strIngredient10:String,
// var strIngredient11:String,
// var strIngredient12:String,
// var strIngredient13:String,
// var strIngredient14:String,
// var strIngredient15:String
//)
//data class Measure(
// var strMeasure1:String,
// var strMeasure2:String,
// var strMeasure3:String,
// var strMeasure4:String,
// var strMeasure5:String,
// var strMeasure6:String,
// var strMeasure7:String,
// var strMeasure8:String,
// var strMeasure9:String,
// var strMeasure10:String,
// var strMeasure11:String,
// var strMeasure12:String,
// var strMeasure13:String,
// var strMeasure14:String,
// var strMeasure15:String
//)
`
Repository:
`
class CocktailRepository constructor(
private val context: Context,
private val cocktailService: CocktailService,
private val cocktailDao: CocktailDao
) {
suspend fun getCocktails(): List<CocktailDetails> {
return try {
if (NetworkUtil.isConnected(context)) {
val cocktails = cocktailService.getCocktails().execute().body()
val roomCocktails = cocktails?.map { mapResponseToDbModel(it) }
cocktailDao.insertAll(roomCocktails ?: return arrayListOf())
}
cocktailDao.getCocktails()
} catch (e: Exception) {
arrayListOf()
}
}
suspend fun getCocktailByName(name: String): CocktailDetails? {
return try {
if (NetworkUtil.isConnected(context)) {
val cocktails = cocktailService.getCocktailByName(name).execute().body()
val roomCocktails = cocktails?.map { mapResponseToDbModel(it) }
cocktailDao.insertAll(roomCocktails ?: return null)
}
return cocktailDao.getCocktailByName(name)
} catch (e: Exception) {
null
}
}
suspend fun updateCocktail(cocktail: CocktailDetails) {
cocktailDao.update(cocktail)
}
private fun mapResponseToDbModel(response: CocktailDetailResponse): CocktailDetails {
return CocktailDetails(
strDrink = response.strDrink ?: "",
strGlass = response.strGlass ?: "",
strDrinkThumb = response.strDrinkThumb,
// strIngredient = response.strIngredient.strIngredient1,
// strMeasure = response.strMeasure.strMeasure1,
strInstructions = response.strInstructions ?: "",
favourite = false
)
}
}
`
Service:
`
interface CocktailService {
#GET("search.php?f=a")
fun getCocktails(): Call<List<CocktailDetailResponse>>
#GET("search.php?s={strDrink}")
fun getCocktailByName(#Path("strDrink") name: String): Call<List<CocktailDetailResponse>>
}
`
NetworkUtil:
`
object NetworkUtil {
fun isConnected(context: Context): Boolean {
val connectivityManager =
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val capabilities =
connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork)
if (capabilities != null) {
return if (capabilities.hasTransport(TRANSPORT_CELLULAR)) {
true
} else if (capabilities.hasTransport(TRANSPORT_WIFI)) {
true
} else capabilities.hasTransport(TRANSPORT_ETHERNET)
}
return false
}
}
`
ViewModel:
`
class CocktailViewModel(
private val cocktailRepository: CocktailRepository
) : ViewModel() {
private val cocktailsListStateFlow = MutableStateFlow<List<CocktailDetails>>(arrayListOf())
private val selectedCocktailStateFlow = MutableStateFlow<CocktailDetails?>(null)
val cocktailsList: StateFlow<List<CocktailDetails>> = cocktailsListStateFlow.asStateFlow()
val selectedCocktail: StateFlow<CocktailDetails?> = selectedCocktailStateFlow.asStateFlow()
suspend fun getCocktails() {
val cocktails = cocktailRepository.getCocktails()
cocktailsListStateFlow.value = cocktails
}
suspend fun getCocktailByName(name: String) {
val cocktail = cocktailRepository.getCocktailByName(name)
selectedCocktailStateFlow.value = cocktail ?: return
}
suspend fun updateFavourites(cocktail: CocktailDetails) {
cocktailRepository.updateCocktail(cocktail)
selectedCocktailStateFlow.value =
selectedCocktailStateFlow.value?.copy(favourite = cocktail.favourite)
}
}
`
MainActivity:
`
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var cocktailService: CocktailService
private lateinit var cocktailRepository: CocktailRepository
lateinit var cocktailViewModel: CocktailViewModel
lateinit var db: RoomDatabase
private val retrofit = Retrofit.Builder()
.baseUrl("https://www.thecocktaildb.com/api/json/v1/1/")
.addConverterFactory(GsonConverterFactory.create())
.build()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
init()
observeData()
if (!NetworkUtil.isConnected(this)) {
Snackbar.make(
binding.root,
"No internet connection, information could be outdated",
Snackbar.LENGTH_LONG
).show()
}
GlobalScope.launch {
cocktailViewModel.getCocktails()
}
}
private fun init() {
db = Room.databaseBuilder(
applicationContext,
AppDatabase::class.java,
"cocktails_database"
).build()
val cocktailDao = (db as AppDatabase).cocktailDao()
cocktailService = retrofit.create(CocktailService::class.java)
cocktailRepository = CocktailRepository(this, cocktailService, cocktailDao)
val cocktailViewModelFactory = CocktailViewModelFactory(cocktailRepository)
cocktailViewModel =
ViewModelProvider(this, cocktailViewModelFactory)[CocktailViewModel::class.java]
}
private fun observeData() {
GlobalScope.launch {
cocktailViewModel.cocktailsList.collect {
runOnUiThread {
val cocktails = it
val sortedCocktails = cocktails.sortedByDescending { it.favourite }
val adapter = CocktailAdapter(sortedCocktails)
binding.cocktailsList.adapter = adapter
binding.tvCocktailsCount.text = "Cocktails: ${it.size}"
}
}
}
}
}
`
I want the Cocktails to be displayed in a list.

Kotlin Paging 3 using Recycler View but List did not showing

I used Paging 3 for my Recycler View that had to show some stories in the application. But while I used Paging 3, the lists did not show the item. Before I used Paging 3, the items had been showing including the details.
StoriesResponseItem.kt
#Entity(tableName = "story")
data class StoriesResponseItem(
#PrimaryKey
#field:SerializedName("id")
val id: String,
#field:SerializedName("photoUrl")
val photoUrl: String,
#field:SerializedName("createdAt")
val createdAt: String,
#field:SerializedName("name")
val name: String,
#field:SerializedName("description")
val description: String,
#field:SerializedName("lon")
val lon: Double? = null,
#field:SerializedName("lat")
val lat: Double? = null
)
ApiService.kt
interface ApiService {
#GET("stories")
suspend fun getPagingStories(
#Header("Authorization") Authorization: String,
#Query("page") page: Int,
#Query("size") size: Int
): List<StoriesResponseItem>
}
StoriesPagingSource.kt
class StoriesPagingSource(private val token: String, private val apiService: ApiService) : PagingSource<Int, StoriesResponseItem>() {
private companion object {
const val INITIAL_PAGE_INDEX = 1
}
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, StoriesResponseItem> {
return try {
val position = params.key ?: INITIAL_PAGE_INDEX
val responseData = apiService.getPagingStories(token, position, params.loadSize)
LoadResult.Page(
data = responseData,
prevKey = if (position == INITIAL_PAGE_INDEX) null else position - 1,
nextKey = if (responseData.isEmpty()) null else position + 1
)
} catch (retryableError: Exception) {
LoadResult.Error(retryableError)
}
}
override fun getRefreshKey(state: PagingState<Int, StoriesResponseItem>): Int? {
return state.anchorPosition?.let { anchorPosition ->
val anchorPage = state.closestPageToPosition(anchorPosition)
anchorPage?.prevKey?.plus(1) ?: anchorPage?.nextKey?.minus(1)
}
}
}
StoriesRepository.kt
class StoriesRepository(private val apiService: ApiService) {
fun getStories(token: String): LiveData<PagingData<StoriesResponseItem>> {
return Pager(
config = PagingConfig(
pageSize = 5
),
pagingSourceFactory = {
StoriesPagingSource(token, apiService)
}
).liveData
}
}
MainViewModel.kt
class MainViewModel(private val storiesRepository: StoriesRepository) : ViewModel() {
private val _stories = MutableLiveData<PagingData<StoriesResponseItem>>()
fun stories(token: String): LiveData<PagingData<StoriesResponseItem>> {
val response = storiesRepository.getStories(token).cachedIn(viewModelScope)
_stories.value = response.value
return response
}
}
class ViewModelFactory(private val context: Context) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(MainViewModel::class.java)) {
#Suppress("UNCHECKED_CAST")
return MainViewModel(Injection.provideRepository(context)) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val mainViewModel: MainViewModel by viewModels {
ViewModelFactory(this)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
if (applicationContext.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
val layoutManager = GridLayoutManager(this, 2)
binding.rvStories.layoutManager = layoutManager
} else {
val layoutManager = LinearLayoutManager(this)
binding.rvStories.layoutManager = layoutManager
}
val pref = AppDataStore.getInstance(dataStore)
val authViewModel = ViewModelProvider(this, ViewModelFactory(pref))[AuthViewModel::class.java]
authViewModel.loginToken().observe(this) { token: String? ->
val loginToken = "Bearer $token"
getData(loginToken)
}
}
private fun getData(loginToken: String) {
val adapter = ListStoriesAdapter()
binding.rvStories.adapter = adapter.withLoadStateFooter(
footer = LoadingStateAdapter {
adapter.retry()
}
)
lifecycleScope.launch {
mainViewModel.stories(loginToken).observe(this#MainActivity) {
adapter.submitData(lifecycle, it)
}
}
}
}
This is my repository for this project :
https://github.com/daffakurnia11/StoryApp

How can i unit test my repository and viewmodel?

I want to unit test my viewmodel and repository but I don't know how I can achieve that. I have made a start wit the viewmodeltestclass but I don't know how I can go further and what the best approach is. Should I also test my endpoint class and mainactivity? can someone help me please?
This is my endpoint class:
interface VenuesEndpoint {
#GET("v2/venues/search")
suspend fun get(
#Query("near") city: String,
#Query("limit") limit: String = Constants.LIMIT,
#Query("radius") radius: String = Constants.RADIUS,
#Query("client_id") id: String = Constants.CLIENT_ID,
#Query("client_secret") secret: String = Constants.CLIENT_SECRET,
#Query("v") date: String
): VenuesMainResponse
}
My repository class:
private val _data: MutableLiveData<VenuesMainResponse?> = MutableLiveData(null)
val data: LiveData<VenuesMainResponse?> get() = _data
suspend fun fetch(city: String, date: String) {
val retrofit = ApiClient()
val api = retrofit.retro.create(VenuesEndpoint::class.java)
try {
val response = api.get(
city = city,
date = date
)
_data.value = response
} catch (e: Exception) {
Log.d(TAG, e.message.toString())
_data.value = null
}
}
}
My viewmodel class:
class VenueViewModel() : ViewModel() {
private val repository = VenuesRepository()
val data: LiveData<VenuesMainResponse?> = repository.data
fun getData(city: String, date: String) {
viewModelScope.launch {
repository.fetch(city, date)
}
}
}
My mainActivity class:
class MainActivity : AppCompatActivity() {
private val viewModel by viewModels<VenueViewModel>()
private lateinit var adapter: HomeAdapter
private var searchData: List<Venue>? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val editText = findViewById<EditText>(R.id.main_search)
viewModel.getData(
city = Constants.CITY,
date = Constants.DATE
)
viewModel.data
.observe(this, Observer {
it.let {
initAdapter()
rv_home.visibility = View.VISIBLE
if (it != null) {
adapter.setData(it.response.venues.sortedBy { myObject -> myObject.name })
searchData = it.response.venues.sortedBy { myObject -> myObject.name }
} else {
Toast.makeText(this, Constants.ERROR_MESSAGE_API, Toast.LENGTH_SHORT).show()
}
}
})
}
My ViewModelTest class:
#RunWith(AndroidJUnit4::class)
class VenueViewModelTest : TestCase() {
private lateinit var viewModel: VenueViewModel
#Before
public override fun setUp() {
super.setUp()
viewModel = VenueViewModel()
}
#Test
fun testVenueViewModel()
{
}
}

One-to-many in Room with Kotlin

The task is to open an activity with notes attached to this diary when you select a single diary.
(one-to-many)
This is how entities in the database look like:
#Entity(tableName = "word_table")
data class Word(#ColumnInfo(name = "word") val word: String,
#ColumnInfo(name = "description") val description : String
)
{
#ColumnInfo(name = "id")
#PrimaryKey(autoGenerate = true)
var id : Long = 0
}
#Entity(tableName = "note_table")
data class Note(#ColumnInfo(name = "note_name") val note : String,
#ColumnInfo(name = "text") val text : String,
#ColumnInfo(name = "diaryId") val diaryId : Long
){
#PrimaryKey(autoGenerate = true)
var idNote : Long = 0
}
Using a data class in NoteRepository.kt
data class NotesAndWords (#Embedded val word : Word,
#Relation(parentColumn = "id", entityColumn = "diaryId")
val notes : List<Note>)
And a Query in WordDao.kt
#Transaction
#Query("SELECT * from word_table ")
fun getSomeNotes() : LiveData<List<NotesAndWords>>
I get the data and save it in the NoteRepository class:
class NoteRepository (private val wordDao : WordDao) {
var allNotes : LiveData<List<NotesAndWords>> = wordDao.getSomeNotes()
suspend fun insertNote(note : Note)
{
wordDao.insertNote(note)
}
}
Then via NoteViewModel.kt passing data to NoteActivity.kt:
class NoteViewModel(application: Application) : AndroidViewModel(application) {
private val repository: NoteRepository
val allNotes: LiveData<List<NotesAndWords>>
init {
val wordsDao = WordRoomDatabase.getDatabase(application, viewModelScope).wordDao()
repository = NoteRepository(wordsDao)
allNotes = repository.allNotes
}
fun insertNote(note: Note) = viewModelScope.launch {
repository.insertNote(note)
}
}
(NoteActivity.kt)
class NoteActivity : AppCompatActivity() {
private val newWordActivityRequestCode = 1
private lateinit var noteViewModel: NoteViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_note)
val adapter = NoteListAdapter(this, intent.getLongExtra("tag", -1) ){
val intent = Intent(this, ClickedActivity::class.java)
intent.putExtra("tag", it.note)
startActivity(intent)
}
recyclerview1.adapter = adapter
recyclerview1.layoutManager = LinearLayoutManager(this)
noteViewModel = ViewModelProvider(this).get(NoteViewModel::class.java)
noteViewModel.allNotes.observe(this, Observer {
adapter.setNotes(it)
})
val fab = findViewById<FloatingActionButton>(R.id.fab)
fab.setOnClickListener {
val intent = Intent(this, NewWordActivity::class.java)
startActivityForResult(intent, newWordActivityRequestCode)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == newWordActivityRequestCode && resultCode == Activity.RESULT_OK)
{
data?.getStringArrayListExtra(NewWordActivity.EXTRA_REPLY)?.let {
val note = Note(it[0], it[1], intent.getLongExtra("tag", -1))
noteViewModel.insertNote(note)
}
}
else
{
Toast.makeText(applicationContext, R.string.empty_not_saved,
Toast.LENGTH_LONG).show()
}
}
Then, in the adapter, I use MutableMap to transform the list so that the key is the name id and the value is the notes selected on request (attached to a specific diary)
NoteListAdapter.kt:
class NoteListAdapter internal constructor(
context: Context,
val wordId: Long,
private val listener : (Note) -> Unit
) : RecyclerView.Adapter<NoteListAdapter.NoteViewHolder>() {
private val inflater: LayoutInflater = LayoutInflater.from(context)
//private val mContext = context
private var notes = emptyList<NotesAndWords>() // Cached copy of words
private var notesMapped = mutableMapOf<Long, List<Note>>()
inner class NoteViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val noteItemView: TextView = itemView.findViewById(R.id.textView1)
private val noteDescriptionView: TextView = itemView.findViewById(R.id.textView)
fun bindView(note: Note, listener : (Note) -> Unit) {
noteItemView.text = note.diaryId.toString()
noteDescriptionView.text = note.text
itemView.setOnClickListener {
listener(note)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NoteViewHolder {
val itemView = inflater.inflate(R.layout.recyclerview_layout, parent,
false)
return NoteViewHolder(itemView)
}
override fun onBindViewHolder(holder: NoteViewHolder, position: Int) {
holder.bindView(notesMapped[wordId]!![position], listener)
}
internal fun setNotes(notes: List<NotesAndWords>) {
this.notes = notes
for (i in this.notes) {
notesMapped[i.word.id] = i.notes
}
notifyDataSetChanged()
}
override fun getItemCount() = notesMapped[wordId]!!.size
}
Database:
#Database(entities = [Word::class, Note::class], version = 2, exportSchema = false)
abstract class WordRoomDatabase : RoomDatabase() {
abstract fun wordDao(): WordDao
private class WordDatabaseCallback(private val scope: CoroutineScope) : RoomDatabase.Callback()
{
override fun onOpen(db: SupportSQLiteDatabase) {
super.onOpen(db)
INSTANCE?.let { database ->
scope.launch {
populateDatabase(database.wordDao())
}
}
}
suspend fun populateDatabase(wordDao: WordDao) {
//wordDao.deleteAll()
//wordDao.deleteAllNotes()
}
}
companion object {
#Volatile
private var INSTANCE: WordRoomDatabase? = null
fun getDatabase(context: Context, scope:CoroutineScope): WordRoomDatabase {
val tempInstance = INSTANCE
if (tempInstance != null) {
return tempInstance
}
val instance = Room.databaseBuilder(context.applicationContext,
WordRoomDatabase::class.java, "word_database")
.addCallback(WordDatabaseCallback(scope))
//.fallbackToDestructiveMigration()
.build()
INSTANCE = instance
return instance
}
}
}
I've created several diaries and one note in each of them, using the buttons to create new diaries and notes. Now, if you select several diaries in turn, then on some attempt to select a diary, a NullPointerException is issued in the adapter, in this line:
override fun getItemCount() = notesMapped[wordId]!!.size
Why is this exception thrown if notesMapped always has the wordId key?
NoteActivity is called from another activity and the diary id is passed to it
This repository on GitHub: https://github.com/Lomank123/RoomDatabase
Edit:
noteViewModel.allNotes.observe(this, Observer {
var getList = emptyList<Note>()
for(i in it)
{
if(i.word.id == wordId)
{
getList = i.notes
break
}
}
adapter.setNotes(getList)
})
I've changed the Observer in NoteActivity and changed setNotes() method in adapter, but now it returns nothing. With for() I get the right notes and give them to adapter.setNotes(). If it doesn't work, how can I get the correct list of notes?
Hi initially the map is empty and the map is returning a null value and you are checking size on a null object.
Also as a good practice do not use a map instead use a list of notes only and pass the list directly.

Categories

Resources