In my task I need to fetch list of bikes.
I made BikesApi and put some bikes into a list.
In MovieRepository implementation I overrided function getBikes that returns now Flow<List>>.
val BikeZ : Flow<List<Bike>> = flow{
while(true){
val lastBikeX = bikeApi.getBikes()
emit(lastBikeX)
kotlinx.coroutines.delay(1000)
}
}
return BikeZ
In BikeViewModel I implemented it like this, It gets MovieRepository (Koin):
public fun getPopularBikes() = flow<Bike> {
val bikesList = repository.getPopularBikes().collect()
return#flow bikesList
}
And when I try in Compose function this:
val viewModel = viewModel<BikeViewModel>()
I got this error:
Cannot create an instance of class BikeViewModel.
java.lang.RuntimeException:
Cannot create an instance of class BikeViewModel
I'm using Koin.
I need list of Movies to implement them into UI.
EDIT:
In app module i did this:
single<BikeApi>{
BikeApiImpl()
}
single<BikeRepository>{
BikeRepositoryImpl(get())
}
viewModel { BikeViewModel(get()) }
Bike api:
class BikeApiImpl : BikeApi{
override suspend fun getPopularBikes(): List<Bike> {
return listOf(
Bike(
id = 1,
name = "Nakamura",
isCheckedOff = true,
bikeType = "MTB",
overview = "None",
picture = R.drawable.nakamura_1,
userScore = 72.0
)
I't wont return list of Bikes.
Initialize your viewModel like this if you are using Koin di:
val viewModel: BikeViewModel by viewModel()
also import
import org.koin.androidx.viewmodel.ext.android.viewModel
Related
I'm fairly new to Jetpack Compose. Currently, I have a ViewModel making 1 network call.
class PlatformViewModel #Inject constructor(
private val getProductListUseCase: GetListUseCase
) : ViewModel()
I had 3 states.
sealed class PlatformState {
object Loading : PlatformState()
data class Success(val listOfProducts: List<Product>) : PlatformState()
object Error : PlatformState()
}
In the UI, it Was easy to handle observing 1 live data.
val state = viewModel.platformState.observeAsState(PlatformState.Loading)
when (state) {
is PlatformState.Success -> SuccessView(listOfProducts = state.listOfProducts)
is PlatformState.Loading -> LoadingView()
is PlatformState.Error -> ErrorView()
}
now, I need to add 1 more network call in viewModel for the same screen
class PlatformViewModel #Inject constructor(
private val getProductListUseCase: GetListUseCase,
private val getHeaderUseCase: GetHeaderUseCase,
) : ViewModel()
-Should I add 3 more states and 1 more live data to observe for the UI, what is the best way to handle this?
Note: both network calls are unrelated but their result populates the same composable.
fun bodyContent(listOfProducts:List<Products>,headerDetails:HeaderDetails){
LazyColumn{
item{ HeaderDetails(details=headerDetails)}
items(listOfProducts.size){
ProductItem()
}
Since the composable function, bodyContent requires both parameters listOfProducts:List<Products>, headerDetails:HeaderDetails.
I would create a data class that holds those values and that class should be sent to the composable function.
data class BodyContentUIData(listOfProducts:List<Products>, headerDetails:HeaderDetails)
The composable should be
fun BodyContent(bodyContentUIData: BodyContentUIData) {
LazyColumn {
item { HeaderDetails(details = bodyContentUIData.headerDetails) }
items(bodyContentUIData.listOfProducts.size) {
ProductItem()
}
}
}
//BTW, composable functions should start with a capital case.
At the view model, You should have a function called getBodyContentData that first calls getHeaderUseCase and if it's a success then call getProductListUseCase after the success you'll be having the listOfProducts and the headerDetails now you create the data class and send it to the composable function.
The view model could look like this:
class PlatformViewModel #Inject constructor(
private val getProductListUseCase: GetListUseCase,
private val getHeaderUseCase: GetHeaderUseCase,
) : ViewModel() {
fun getBodyContentData() {
getHeaderUseCase().onSuccess { headerDetails ->
getProductListUseCase().onSuccess { listOfProducts ->
_bodyContentLiveData.value = SuccessView(BodyContent(headerDetails, listOfProducts))
}
}
}
}
This way you have just 1 live data and 3 states for the composable.
How do I have multiple callback function back to the Activity/Fragment of a RecyclerView?
I have multiple options for each item in the RecyclerView (Edit, Delete, CheckedAsComplete, View) and I would like to have callback function for each of them in the Activity/Fragment of the RecyclerView.
Here is a link of how I got one callback in the Adapter: https://www.geeksforgeeks.org/kotlin-lambda-functions-for-recyclerview-adapter-callbacks-in-android/
I just need to know if it is possible to have multiple callbacks in the adapter and if so, how do I implement it?
My Activity's Adapter Code:
val adapter = ProductAdapter(this) {
deleteProduct(it),
editProduct(it),
viewProduct(it),
checkAsComplete(it)
}
Here is my Adapter's Constructor:
class ProductAdapter(
private var context: Context,
private val deleteProduct: (ItemTable) -> Unit,
private val editProduct: (ItemTable) -> Unit,
private val viewProduct: (ItemTable) -> Unit,
private val checkedAsComplete: (ItemTable) -> Unit
): RecyclerView.Adapter<ProductAdapter.ItemProductViewHolder>() {
// Rest of RecyclerView Adapter Code
}
I'm pretty new to kotlin so I would really appreciate your help!
You can use curly braces out only for the last callback of the list.
Assuming you declared the following methods in your activity :
fun deleteProduct(itemTable: ItemTable)
fun editProduct(itemTable: ItemTable)
fun checkAsComplete(itemTable: ItemTable)
fun viewProduct(itemTable: ItemTable)
You can use named parameters and you have two choices
With method reference
val adapter = ProductAdapter(
context = this,
deleteProduct = ::deleteProduct,
editProduct = ::editProduct,
viewProduct = ::viewProduct,
checkAsComplete = ::checkAsComplete
)
With lambda
val adapter = ProductAdapter(
context = this,
deleteProduct = { deleteProduct(it) },
editProduct = { editProduct(it) },
viewProduct = { viewProduct(it) },
checkAsComplete = { checkAsComplete(it) }
)
You can use different approach. This does not depend on how many events you have. For example with enum class you can use single callback with many options
class ProductAdapter(private val clickEvent: (ClickEvent, ItemTable) -> Unit):
RecyclerView.Adapter<ProductAdapter.ItemProductViewHolder>() {
enum class ClickEvent {
DELETE,
EDIT,
VIEW,
COMPLETE
}
}
Usage:
val adapter = ProductAdapter{ event, item ->
when(event){
DELETE -> deleteProduct(item)
....//All other enum values
}
}
class MainAcitvity
fun roomSetup(){
setFavouriteDao = FavouriteDatabase.getDatabase(applicationContext).setFavouriteDao()
repositoryRoom = LorRepository(setFavouriteDao)
viewModelRoom = ViewModelProvider(this,LorViewModelFactory(repositoryRoom!!)).get(LorViewModel::class.java)
}
override fun onMovieClick(position: Int) {
roomSetup()
Toast.makeText(this#MainActivity, "clicked!"+position, Toast.LENGTH_SHORT).show()
var setFavourite = SetFavourite(movieResponse!!.docs[position].Id.toString(),movieResponse!!.docs[position].name.toString())
viewModelRoom.addToFavourites(setFavourite)
}
class ViewModel
fun addToFavourites(setFavourite: SetFavourite){
viewModelScope.launch(Dispatchers.IO){
lorRepository.addToFavourites(setFavourite)
}
}
class LorRepository( favouriteDao: SetFavouriteDao?) {
var favouriteDao : SetFavouriteDao
init {
this.favouriteDao = favouriteDao!!
}
private var lorApi: LORApi.LorCalls? = null
constructor(lorApi2 : LORApi.LorCalls?, favouriteDao: SetFavouriteDao?) : this(favouriteDao){
this.lorApi = lorApi2
}
I have 2 constructors
one to initialize room other for initializing retrofit
I am Also doubtful about the constructor in Repository. Thoose are made for 2 different purposes, one for initializing room database and other for repository. but everytime I create one object of room/retrofit the second constructor , when called, fills it with null values
My questions for you are:
Why do you to initialize retrofit and room's dao in a separate constructor?
What is it that you try to achieve?
In your code you only call to initialize dao constructor, therefore lorApi is null.
For your case you wouldn't want to initialize them separately.
Change your code to this:
class LorRepository(private val lorApi : LORApi.LorCalls, private val favouriteDao: SetFavouriteDao)
I'm trying to combine three different flows in my ViewModel to make a list of items that will then be displayed on a RecyclerView in a fragment. I found out that when navigating to the screen, when there is no data in the table yet, the flow for testData1 doesn't emit the data in the table. Happens probably 1/5 of the time. I assume it's a timing issue because it only happens so often, but I don't quite understand why it happens. Also, this only happens when I'm combining flows so maybe I can only have so many flows in one ViewModel?
I added some code to check to see if the data was in the table during setListData() and it's definitely there. I can also see the emit happening but, there is no data coming from room. Any guidance would be greatly appreciated!
Versions I'm using:
Kotlin: 1.4.20-RC
Room: 2.3.0-alpha03
Here is my ViewModel
class DemoViewModel #Inject constructor(
demoService: DemoService,
private val demoRepository: DemoRepository
) : ViewModel() {
private val _testData1 = demoRepository.getData1AsFlow()
private val _testData2 = demoRepository.getData2AsFlow()
private val _testData3 = demoRepository.getData3AsFlow()
override val mainList = combine(_testData1, _testData2, _testData3) { testData1, testData2, testData3 ->
setListData(testData1, testData2, testData3)
}.flowOn(Dispatchers.Default)
.asLiveData()
init {
viewModelScope.launch(Dispatchers.IO) {
demoService.getData()
}
}
private suspend fun setListData(testData1: List<DemoData1>, testData2: List<DemoData2>, testData3: List<DemoData3>): List<CombinedData> {
// package the three data elements up to one list of rows
...
}
}
And here is my Repository/DAO layer (repeats for each type of data)
#Query("SELECT * FROM demo_data_1_table")
abstract fun getData1AsFlow() : Flow<List<DemoData1>>
I was able to get around this issue by removing flowOn in the combine function. After removing that call, I no longer had the issue.
I still wanted to run the setListData function on the default dispatcher, so I just changed the context in the setListData instead.
class DemoViewModel #Inject constructor(
demoService: DemoService,
private val demoRepository: DemoRepository
) : ViewModel() {
private val _testData1 = demoRepository.getData1AsFlow()
private val _testData2 = demoRepository.getData2AsFlow()
private val _testData3 = demoRepository.getData3AsFlow()
override val mainList = combine(_testData1, _testData2, _testData3) { testData1, testData2, testData3 ->
setListData(testData1, testData2, testData3)
}.asLiveData()
init {
viewModelScope.launch(Dispatchers.IO) {
demoService.getData()
}
}
private suspend fun setListData(testData1: List<DemoData1>, testData2: List<DemoData2>, testData3: List<DemoData3>): List<CombinedData> = withContext(Dispatchers.Default) {
// package the three data elements up to one list of rows
...
}
}
I am using Room for handling db entities and I am adapting the code from the WordRoom example from adroid developers.
I understand that in order to eprform operations that can take a long time I have to use coroutines, and this seems to work fine for inserting and deleting objects into the database. In the main activity I have a recyclerview that onCreate gets binded to its layoutmanager and to the ViewModelProvider.
In the adapter I set an onClick listener to get the current ID of the object in the recycled view>
holder.mealItemView.setOnClickListener {
(callerContext as MainActivity).getID(current)
}
the getID from the main activity starts a new activity that should retrieve the element from the database and display its properties:
fun getID(meal:Meal){
val intent = Intent(applicationContext, ActivtyViewMeal::class.java)
intent.putExtra("mealId", meal.id.toString())
startActivity(intent)
}
Then in the ActivityViewMeal in the oncreate I get the intent and add an observer to the variable that should store the Entity from the database:
mealViewModel = ViewModelProvider(this).get(MealViewModel::class.java)
mealViewModel.aMeal.observe(this, Observer {meal ->
meal?.let {Log.d(...)
dataIn.text = it.mealAddDate.toString()})
mealViewModel.getSingleContentById(mealID.toInt())
and after binding the variable with the observer I try to retrieve the data.
My issue is that the log never gets executed.
aMeal is declared inside the ViewModel, a separate kotlin class:
class MealViewModel(application: Application) : AndroidViewModel(application) {
private val repository: MealRepository
val allMeals: LiveData<List<Meal>>
var aMeal: LiveData<Meal>
init {
val mealsDao = MealRoomDatabase.getDatabase(application, viewModelScope).mealDao()
repository = MealRepository(mealsDao)
allMeals = repository.allMeals
aMeal = repository.aMeal
}
...
fun getSingleContentById(id: Int)=viewModelScope.launch(Dispatchers.IO) {
repository.getSingleContentById(id)
}
}
I understand that the coroutine cannot return a value and shall not block.
The meal repository class is defined as follow:
/**
* Abstracted Repository as promoted by the Architecture Guide.
* https://developer.android.com/topic/libraries/architecture/guide.html
*/
class MealRepository(private val mealDao: MealDao) {
val allMeals: LiveData<List<Meal>> = mealDao.getAllContentById()
var aMeal: LiveData<Meal> = mealDao.getSingleContentById(0) //init to avoid null pointer errors
...
#Suppress("RedundantSuspendModifier")
#WorkerThread
fun getSingleContentById(id:Int){
aMeal = mealDao.getSingleContentById(id)
// no return from here
}
and to conclude, getSingleContentById is defined inside the DAO class, that is in another kotlin file.
#Query("SELECT * FROM meals_table WHERE id=:id")
fun getSingleContentById(id:Int):LiveData<Meal>
I have no hint on why I don't get the aMeal updated, while the variable allmeals gets updated correctly.
Any hint would be gladly appreciated.
You can change you DB operation to be suspend and return Meal object:
#Query("SELECT * FROM meals_table WHERE id=:id")
suspend fun getSingleContentById(id:Int): Meal
In your repository make getSingleContentById function suspend as well:
class MealRepository(private val mealDao: MealDao) {
suspend fun getSingleContentById(id: Int): Meal {
aMeal = mealDao.getSingleContentById(id)
return aMeal
}
}
In your MealViewModel make aMeal of type MutableLiveData and update it in getSingleContentById function:
class MealViewModel(application: Application) : AndroidViewModel(application) {
private val repository: MealRepository
val aMeal: MutableLiveData<Meal> = MutableLiveData()
init {
val mealsDao = MealRoomDatabase.getDatabase(application, viewModelScope).mealDao()
repository = MealRepository(mealsDao)
}
fun getSingleContentById(id: Int) = viewModelScope.launch(Dispatchers.Main) {
val meal = repository.getSingleContentById(id)
aMeal.postValue(meal)
}
}