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.
Related
I'm learning paging 3, but the data from the API doesn't appear. My code is like below:
interface PokeAPI {
#GET("pokemon")
fun getPokemonList() : Call<PokemonList>
#GET("pokemon")
fun getAllPokemon(
#Query("limit") limit: Int,
#Query("offset") offset: Int) : PokemonList
#GET("pokemon/{name}")
fun getPokemonInfo(
#Path("name") name: String
) : Call<Pokemon>
}
class PokePagingSource(private val apiService: PokeAPI): PagingSource<Int, Result>() {
private companion object {
const val INITIAL_PAGE_INDEX = 1
}
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Result> {
return try {
val position = params.key ?: INITIAL_PAGE_INDEX
val responseData = apiService.getAllPokemon(position, params.loadSize)
if (responseData.results.isEmpty()) {
Log.e("Response Succeed!", responseData.results.toString())
} else {
Log.e("Response Failed!", responseData.results.toString())
}
LoadResult.Page(
data = responseData.results,
prevKey = if (position == INITIAL_PAGE_INDEX) null else position - 1,
nextKey = if (responseData.results.isNullOrEmpty()) null else position + 1
)
} catch (exception: Exception) {
return LoadResult.Error(exception)
}
}
override fun getRefreshKey(state: PagingState<Int, Result>): Int? {
return state.anchorPosition?.let { anchorPosition ->
val anchorPage = state.closestPageToPosition(anchorPosition)
anchorPage?.prevKey?.plus(1) ?: anchorPage?.nextKey?.minus(1)
}
}
}
class PokemonRepository(private val apiService: PokeAPI) {
fun getAllPokemon(): LiveData<PagingData<Result>>{
return Pager(
config = PagingConfig(
pageSize = 10
),
pagingSourceFactory = {
PokePagingSource(apiService)
}
).liveData
}
}
object Injection {
private val api by lazy { RetrofitClient().endpoint }
fun provideRepository(): PokemonRepository {
return PokemonRepository(api)
}
}
class PokemonViewModel(pokemonRepository: PokemonRepository) : ViewModel() {
val allPokemonList: LiveData<PagingData<Result>> =
pokemonRepository.getAllPokemon().cachedIn(viewModelScope)
}
class ViewModelFactory : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(PokemonViewModel::class.java)) {
#Suppress("UNCHECKED_CAST")
return PokemonViewModel(Injection.provideRepository()) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
`class PokemonPagingAdapter(private val context: Context) :
PagingDataAdapter<Result, PokemonPagingAdapter.ViewHolder>(DIFF_CALLBACK) {
private var onItemClick: OnAdapterListener? = null
fun setOnItemClick(onItemClick: OnAdapterListener) {
this.onItemClick = onItemClick
}
class ViewHolder(val binding: AdapterPokemonBinding) : RecyclerView.ViewHolder(binding.root) {
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(
AdapterPokemonBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val pokemonData = getItem(position)
if (pokemonData != null) {
holder.binding.apply {
val number = if (pokemonData.url.endsWith("/")) {
pokemonData.url.dropLast(1).takeLastWhile { it.isDigit() }
} else {
pokemonData.url.takeLastWhile { it.isDigit() }
}
val url = "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/${number}.png"
Glide.with(context)
.load(url)
.transition(DrawableTransitionOptions.withCrossFade())
.centerCrop()
.circleCrop()
.into(ivPokemon)
tvNamePokemon.text = pokemonData.name
btnDetail.setOnClickListener {
onItemClick?.onClick(pokemonData, pokemonData.name, url)
}
}
}
}
companion object {
val DIFF_CALLBACK = object : DiffUtil.ItemCallback<Result>() {
override fun areItemsTheSame(
oldItem: Result,
newItem: Result
): Boolean {
return oldItem == newItem
}
override fun areContentsTheSame(
oldItem: Result,
newItem: Result
): Boolean {
return oldItem.name == newItem.name
}
}
}
interface OnAdapterListener {
fun onClick(data: Result, name: String, url: String)
}
}`
class FragmentPokemon: Fragment(R.layout.fragment_pokemon) {
private var _binding : FragmentPokemonBinding? = null
private val binding get() = _binding!!
private lateinit var dataPagingAdapter: PokemonPagingAdapter
private val viewModel: PokemonViewModel by viewModels {
ViewModelFactory()
}
private lateinit var comm: Communicator
override fun onStart() {
super.onStart()
getData()
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
_binding = FragmentPokemonBinding.bind(view)
val toolBar = requireActivity().findViewById<View>(R.id.tool_bar)
toolBar.visibility = View.VISIBLE
val navBar = requireActivity().findViewById<BottomNavigationView>(R.id.bottom_navigation)
navBar.visibility = View.VISIBLE
comm = requireActivity() as Communicator
setupListPokemon()
}
private fun setupListPokemon(){
dataPagingAdapter = PokemonPagingAdapter(requireContext())
dataPagingAdapter.setOnItemClick(object: PokemonPagingAdapter.OnAdapterListener{
override fun onClick(data: Result, name: String, url: String) {
comm.passDataCom(name, url)
}
})
binding.apply {
rvPokemon.layoutManager = LinearLayoutManager(context)
rvPokemon.setHasFixedSize(true)
rvPokemon.adapter = dataPagingAdapter
}
}
private fun getData(){
viewModel.allPokemonList.observe(viewLifecycleOwner){
dataPagingAdapter.submitData(lifecycle, it)
binding.btnCoba.setOnClickListener { btn ->
if (it == null){
Log.e("ResponseFailed", it.toString())
} else Log.e("ResponseSucceed", it.toString())
}
}
}
}
What's the reason? I have followed the step by step implementation of paging 3 but the data still doesn't appear either.
I don't know the API you are using, but it seems that you are using it incorrectly. The getAllPokemon method has limit and offset parameters and you are calling it like apiService.getAllPokemon(position, params.loadSize), so you are using position as a limit and params.loadSize as an offset.
You should pass params.loadSize as a limit, rename INITIAL_PAGE_INDEX to INITIAL_OFFSET and set it to 0, since your API uses offsets instead of pages (at least it seems so from what you provided). The load function should then look something like this:
// get current offset
val offset = params.key ?: INITIAL_OFFSET
val responseData = apiService.getAllPokemon(limit = params.loadSize, offset = offset)
val prevKey = offset - params.loadSize
val nextKey = offset + params.loadSize
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)
}
}
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
I am trying to display data from IconFinder API. It seems to be ItemKeyedDataSource for me and I used Paging3 to display the data as it's mentioned in the official docs.
Here is code, please check if there're any issues with the implementation I have done and where is the mistake.
IconSetsRemoteMediator
#OptIn(ExperimentalPagingApi::class)
class IconSetsRemoteMediator(
private val query: String?,
private val database: IconsFinderDatabase,
private val networkService: IconFinderAPIService
) : RemoteMediator<Int, IconSetsEntry>() {
private val TAG: String? = IconSetsRemoteMediator::class.simpleName
private val iconSetsDao = database.iconSetsDao
private val remoteKeysDao = database.remoteKeysDao
override suspend fun initialize(): InitializeAction {
// Load fresh data when ever the app is open new
return InitializeAction.LAUNCH_INITIAL_REFRESH
}
override suspend fun load(
loadType: LoadType,
state: PagingState<Int, IconSetsEntry>
): MediatorResult {
val iconSetID = when (loadType) {
LoadType.REFRESH -> {
null
}
LoadType.PREPEND -> {
return MediatorResult.Success(
endOfPaginationReached = true
)
}
LoadType.APPEND -> {
Log.d(TAG, "LoadType.APPEND")
val lastItem = state.lastItemOrNull()
if (lastItem == null) {
return MediatorResult.Success(
endOfPaginationReached = true
)
}
// Get the last item from the icon-sets list and return its ID
lastItem.iconset_id
}
}
try {
// Suspending network load via Retrofit.
val response = networkService.getAllPublicIconSets(after = iconSetID)
val iconSets = response.iconsets
val endOfPaginationReached = iconSets == null || iconSets.isEmpty()
database.withTransaction {
if (loadType == LoadType.REFRESH) {
// Delete the data in the database
iconSetsDao.deleteAllIconSets()
//remoteKeysDao.deleteRemoteKeys()
}
Log.d(TAG, "iconSets = ${iconSets?.size}")
Log.d(TAG, "endOfPaginationReached = $endOfPaginationReached")
Log.d(TAG, "state.anchorPosition = ${state.anchorPosition}")
Log.d(TAG, "state.pages = ${state.pages.size}")
val time = System.currentTimeMillis()
/*val remoteKeys = iconSets!!.map {
RemoteKeysEntry(it.iconset_id, time)
}*/
// Insert new IconSets data into database, which invalidates the current PagingData,
// allowing Paging to present the updates in the DB.
val data = iconSets!!.mapAsIconSetsEntry()
iconSetsDao.insertAllIconSets(data)
// Insert the remote key values which set the time at which the data is
// getting updated in the DB
//remoteKeysDao.insertRemoteKeys(remoteKeys)
}
return MediatorResult.Success(endOfPaginationReached = endOfPaginationReached)
} catch (exception: IOException) {
return MediatorResult.Error(exception)
} catch (exception: HttpException) {
return MediatorResult.Error(exception)
}
}
}
IconFinderRepository
class IconFinderRepository(
private val service: IconFinderAPIService,
private val database: IconsFinderDatabase
) {
private val TAG: String? = IconFinderRepository::class.simpleName
fun getPublicIconSets(): Flow<PagingData<IconSetsEntry>> {
Log.d(TAG, "New Icon Sets query")
val pagingSourceFactory = { database.iconSetsDao.getIconSets() }
#OptIn(ExperimentalPagingApi::class)
return Pager(
config = PagingConfig(pageSize = NUMBER_OF_ITEMS_TO_FETCH, enablePlaceholders = false),
remoteMediator = IconSetsRemoteMediator(
query = null,
database,
service
),
pagingSourceFactory = pagingSourceFactory
).flow
}
companion object {
const val NUMBER_OF_ITEMS_TO_FETCH = 20
}
}
IconSetViewHolder
class IconSetViewHolder private constructor(val binding: RecyclerItemIconSetBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(iconSetsEntry: IconSetsEntry?) {
if (iconSetsEntry == null) {
//Show the Loading UI
} else {
binding.model = iconSetsEntry
binding.executePendingBindings()
}
}
companion object {
fun from(parent: ViewGroup): IconSetViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = RecyclerItemIconSetBinding.inflate(layoutInflater, parent, false)
return IconSetViewHolder(binding)
}
}
}
IconSetAdapter
class IconSetAdapter : PagingDataAdapter<UiModel.IconSetDataItem, ViewHolder>(UI_MODEL_COMPARATOR) {
companion object {
private val UI_MODEL_COMPARATOR =
object : DiffUtil.ItemCallback<UiModel.IconSetDataItem>() {
override fun areContentsTheSame(
oldItem: UiModel.IconSetDataItem,
newItem: UiModel.IconSetDataItem
): Boolean {
return oldItem.iconSetsEntry.iconset_id == newItem.iconSetsEntry.iconset_id
}
override fun areItemsTheSame(
oldItem: UiModel.IconSetDataItem,
newItem: UiModel.IconSetDataItem
): Boolean =
oldItem == newItem
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return if (viewType == R.layout.recycler_item_icon_set) {
IconSetViewHolder.from(parent)
} else {
IconSetViewHolder.from(parent)
}
}
override fun getItemViewType(position: Int): Int {
return when (getItem(position)) {
is UiModel.IconSetDataItem -> R.layout.recycler_item_icon_set
null -> throw UnsupportedOperationException("Unknown view")
else -> throw UnsupportedOperationException("Unknown view")
}
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val uiModel = getItem(position)
uiModel.let {
when (uiModel) {
is UiModel.IconSetDataItem -> (holder as IconSetViewHolder).bind(uiModel.iconSetsEntry)
}
}
}
}
HomeFragmentViewModel
class HomeFragmentViewModel(application: Application) : AndroidViewModel(application) {
private val TAG: String? = HomeFragmentViewModel::class.simpleName
private val repository: IconFinderRepository = IconFinderRepository(
IconFinderAPIService.create(),
IconsFinderDatabase.getInstance(application)
)
private var iconSetsQueryResult: Flow<PagingData<UiModel.IconSetDataItem>>? = null
fun iconSetsQuery(): Flow<PagingData<UiModel.IconSetDataItem>> {
val newResult: Flow<PagingData<UiModel.IconSetDataItem>> = repository.getPublicIconSets()
.map { pagingData -> pagingData.map { UiModel.IconSetDataItem(it) } }
.cachedIn(viewModelScope)
iconSetsQueryResult = newResult
return newResult
}
/**
* Factory for constructing HomeFragmentViewModel
*/
class Factory(private val application: Application) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(HomeFragmentViewModel::class.java)) {
return HomeFragmentViewModel(application) as T
}
throw IllegalArgumentException("Unable to construct ViewModel")
}
}
}
sealed class UiModel {
data class IconSetDataItem(val iconSetsEntry: IconSetsEntry) : UiModel()
}
IconSetFragment: This is one of the fragments implemented as part of ViewPager. Its parent is a Fragment in an Activity.
class IconSetFragment : Fragment() {
private val TAG: String = IconSetFragment::class.java.simpleName
/**
* Declaring the UI Components
*/
private lateinit var binding: FragmentIconSetBinding
private val viewModel: HomeFragmentViewModel by viewModels()
private val adapter = IconSetAdapter()
private var job: Job? = null
companion object {
fun newInstance(): IconSetFragment {
return IconSetFragment()
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Get a reference to the binding object
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_icon_set, container, false)
Log.d(TAG, "onCreateView")
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initAdapter()
job?.cancel()
job = viewLifecycleOwner.lifecycleScope.launch {
viewModel.iconSetsQuery().collectLatest {
adapter.submitData(it)
Log.d(TAG, "collectLatest $it")
}
}
}
private fun initAdapter() {
binding.rvIconSetList.adapter = adapter
/*.withLoadStateHeaderAndFooter(
header = LoadStateAdapter(), // { adapter.retry() },
footer = LoadStateAdapter { adapter.retry() }
)*/
}
}
IconSetsDao
#Dao
interface IconSetsDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAllIconSets(iconSets: List<IconSetsEntry>)
#Query("SELECT * FROM icon_sets_table")
fun getIconSets(): PagingSource<Int, IconSetsEntry>
#Query("DELETE FROM icon_sets_table")
suspend fun deleteAllIconSets()
}
This is the Logcat screenshot, the load() method is being invoked without any scrolling action.
I have the similar issue, seems the recursive loading issue is fixed by setting the recyclerView.setHasFixedSize(true)
I'm tying to parse JSON in recyclerview. App compiles fine but it's outputting empty/blank screen
BlogAdapter.kt
class BlogAdapter(private val blogList: List<Blog>) : RecyclerView.Adapter<BlogAdapter.ViewHolder>() {
override fun getItemCount()= blogList.size
private var mContext: Context? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
this.mContext=parent.context;
return ViewHolder(
LayoutInflater.from(parent.context).inflate(
R.layout.character_item,
parent,
false
)
)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val mBlog = this.blogList[position]
if (mBlog.img != null) {
Glide.with(mContext!!)
.load(mBlog.img)
.into(holder.ivThumbnail)
}
if (mBlog.name != null) {
holder.tvTitle.text = mBlog.name
println("Log: Kabe "+mBlog.name)
}
}
class ViewHolder(itemView:View):RecyclerView.ViewHolder(itemView){
val ivThumbnail:ImageView = itemView.findViewById(R.id.ivThumbnail);
val tvTitle:TextView = itemView.findViewById(R.id.tvTitle);
}
}
MainActivity.kt
class MainActivity : AppCompatActivity() {
var mainViewModel: MainViewModel? = null
var mBlogAdapter: BlogAdapter? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mainViewModel = ViewModelProvider(this).get(MainViewModel::class.java)
getPopularBlog()
swipe_refresh.setOnRefreshListener { getPopularBlog() }
}
private fun getPopularBlog() {
swipe_refresh.isRefreshing = false
mainViewModel!!.allBlog.observe(this, Observer { charactersList ->
prepareRecyclerView(charactersList)
})
}
private fun prepareRecyclerView(blogList: List<Blog>) {
mBlogAdapter = BlogAdapter(blogList)
if (this.resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
blogRecyclerView.layoutManager = LinearLayoutManager(this)
} else {
blogRecyclerView.layoutManager = GridLayoutManager(this, 4)
}
blogRecyclerView.itemAnimator = DefaultItemAnimator()
blogRecyclerView.adapter = mBlogAdapter
}
}
My Json file looks like this:
[
{
"id": 1,
"name": "potter",
"img": "https://images.example.com/potter.jpg"
},
{ …}
]
I've created it based on this tutorial: https://itnext.io/kotlin-wrapping-your-head-around-livedata-mutablelivedata-coroutine-networking-and-viewmodel-b552c3a74eec
Any suggestions please
EDIT:
class BlogRepository() {
private var character = mutableListOf<ABCCharacters>()
private var mutableLiveData = MutableLiveData<List<ABCCharacters>>()
val completableJob = Job()
private val coroutineScope = CoroutineScope(Dispatchers.IO + completableJob)
private val thisApiCorService by lazy {
RestApiService.createCorService()
}
fun getMutableLiveData():MutableLiveData<List<ABCCharacters>> {
coroutineScope.launch {
val request = thisApiCorService.getPopularBlog()
withContext(Dispatchers.Main) {
try {
val response = request.await()
val mBlogWrapper = response;
if (/*mBlogWrapper != null &&*/ mBlogWrapper.isNotEmpty()) {
character = mBlogWrapper as MutableList<ABCCharacters>
mutableLiveData.value = character
}
} catch (e: HttpException) {
// Log exception //
} catch (e: Throwable) {
// Log error //)
}
}
}
return mutableLiveData;
}
}
You forget to call notifyDataSetChanged, when you setup your RecyclerView widget. Below the full method call, to make it works.
private fun prepareRecyclerView(blogList: List<Blog>) {
mBlogAdapter = BlogAdapter(blogList)
if (this.resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
blogRecyclerView.layoutManager = LinearLayoutManager(this)
} else {
blogRecyclerView.layoutManager = GridLayoutManager(this, 4)
}
blogRecyclerView.itemAnimator = DefaultItemAnimator()
blogRecyclerView.adapter = mBlogAdapter
mBlogAdapter.notifyDataSetChanged()
}
Try using below implementation:
class MainActivity : AppCompatActivity() {
lateinit var mainViewModel: MainViewModel
var mBlogAdapter: BlogAdapter? = null
var blogList: List<Blog> = arrayListOf()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mainViewModel = ViewModelProvider(this).get(MainViewModel::class.java)
// init your RV here
prepareRecyclerView()
getPopularBlog()
swipe_refresh.setOnRefreshListener { mainViewModel.getAllBlog() }
}
private fun getPopularBlog() {
swipe_refresh.isRefreshing = false
mainViewModel.getAllBlog().observe(this, Observer { charactersList ->
blogList = charactersList
mBlogAdapter?.notifyDataSetChanged()
})
}
private fun prepareRecyclerView() {
mBlogAdapter = BlogAdapter(blogList)
if (this.resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
blogRecyclerView.layoutManager = LinearLayoutManager(this)
} else {
blogRecyclerView.layoutManager = GridLayoutManager(this, 4)
}
blogRecyclerView.itemAnimator = DefaultItemAnimator()
blogRecyclerView.adapter = mBlogAdapter
}
}
Modify your view model like below:
class MainViewModel() : ViewModel() {
val characterRepository= BlogRepository()
fun getAllBlog(): MutableLiveData<List<ABCCharacters>> {
return characterRepository.getMutableLiveData()
}
override fun onCleared() {
super.onCleared()
characterRepository.completableJob.cancel()
}
}