I'm parsing API and it's logging in the logcat, but I have a problem while retrieving it and using that data in a recycleview. These are my code snippets:
class MainActivity : AppCompatActivity() {
private val users = arrayListOf<User>()
private lateinit var adapter: RecyclerViewAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
init()
}
private fun init() {
adapter = RecyclerViewAdapter(users)
recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.adapter = adapter
val myViewModel : UsersViewModel by viewModel()
myViewModel.getAllUsers().observe(this, Observer {
users.add(it)
adapter.notifyDataSetChanged()
})
myViewModel.getUsers()
d("allUsers", users.size.toString())
}
}
I cannot set the data in a recyclerview, can anyone give me a hint? I could not find a proper source or code snippet to understand how I'm able to parse the data using a converter.
class UsersRequest {
private var retrofit = Retrofit.Builder()
.baseUrl("https://reqres.in/api/")
.addConverterFactory(ScalarsConverterFactory.create())
.build()
private var service = retrofit.create(ApiService::class.java)
interface ApiService {
#GET("users?page=1")
fun getRequest(): Call<String>
}
fun getRequest(callback: CustomCallback) {
val call = service.getRequest()
call.enqueue(onCallback(callback))
}
private fun onCallback(callback: CustomCallback): Callback<String> = object : Callback<String> {
override fun onFailure(call: Call<String>, t: Throwable) {
d("response", "${t.message}")
callback.onFailure(t.message.toString())
}
override fun onResponse(call: Call<String>, response: Response<String>) {
d("response", "${response.body()}")
callback.onSuccess(response.body().toString())
}
}
}
class RecyclerViewAdapter(private val users: ArrayList<User>) :
RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(
LayoutInflater.from(parent.context).inflate(
R.layout.user_layout,
parent,
false
)
)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
return holder.onBind()
}
override fun getItemCount() = users.size
private lateinit var user:User
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun onBind() {
user = users[adapterPosition]
itemView.name.text = user.first_name
}
}
}
User(
val id: Int,
val email: String,
val first_name: String,
val last_name: String,
val avatar: String
)
with adapter class RecyclerViewAdapter(private val users: MutableList<User>) : RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder>() { ...
Add this method to your adapter :
fun setUsers(usersList: List<User>) {
this.users.clear()
this.users.addAll(usersList)
notifyDataSetChanged()
}
and in MainActivity simply put :
myViewModel.getAllUsers().observe(this, Observer {
users -> hideLoading() // If you have a progress bar, here you can hide it
users?.let {
adapter.setUsers(users) }
})
Related
I am implementing a recycler view with diff util, but the first time I send the list to the adapter, the recycler view is not notified about the new list. Only the second time I update the list does the recycler receive the notification. I tried several solutions I found in similar questions but none worked. If anyone can help, thanks in advance.
Here is my code:
Adapter:
class UserListAdapter : Adapter<UserListItemViewHolder>() {
private var mUsers = mutableListOf<User>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserListItemViewHolder =
UserListItemViewHolder.create(parent)
override fun onBindViewHolder(holder: UserListItemViewHolder, position: Int) {
holder.bind(mUsers[position])
}
override fun getItemCount(): Int = mUsers.size
fun updateUsersList(users: List<User>) {
val diffCallback = UserListDiffCallback(this.mUsers, users)
val diffResult = DiffUtil.calculateDiff(diffCallback)
this.mUsers.clear()
this.mUsers.addAll(users)
diffResult.dispatchUpdatesTo(this)
}
}
UsersListDiffCallback:
class UserListDiffCallback(
private val oldList: List<User>,
private val newList: List<User>
) : DiffUtil.Callback() {
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return oldList[oldItemPosition].username == newList[newItemPosition].username
}
override fun getOldListSize(): Int {
return oldList.size
}
override fun getNewListSize(): Int {
return newList.size
}
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return true
}
}
ViewModel:
#HiltViewModel
class MainViewModel #Inject constructor(
private val getUsersUseCase: GetUsersUseCase
) : ViewModel() {
private val _usersResultStatus = MutableLiveData<ResultStatus>(NotLoading)
val usersResultStatus: LiveData<ResultStatus>
get() = _usersResultStatus
private val _users = MutableLiveData<MutableList<User>?>()
val users: LiveData<MutableList<User>?>
get() = _users
fun getUsers() {
viewModelScope.launch {
try {
_usersResultStatus.value = Loading
_users.value = getUsersUseCase()
} catch (error: Throwable) {
_usersResultStatus.value = Error(error)
} finally {
_usersResultStatus.value = NotLoading
}
}
}
}
And where I update the list in the activity:
#AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private var _binding: ActivityMainBinding? = null
private val binding: ActivityMainBinding get() = _binding!!
private val viewModel: MainViewModel by viewModels()
private val usersAdapter = UserListAdapter()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
_binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
initUsersAdapter()
observeStates()
viewModel.getUsers()
}
private fun initUsersAdapter() {
with(binding.recyclerView) {
setHasFixedSize(true)
adapter = usersAdapter
}
}
private fun observeStates() {
viewModel.usersResultStatus.observe(this) { resultStatus ->
setProgressbarVisibility(resultStatus is Loading)
if (resultStatus is Error) {
Toast.makeText(
this#MainActivity,
getString(R.string.error),
Toast.LENGTH_SHORT
).show()
}
}
viewModel.users.observe(this) { users ->
users?.let {
usersAdapter.updateUsersList(users.toMutableList())
}
}
}
private fun setProgressbarVisibility(show: Boolean) {
binding.progressBar.visibility = if (show) View.VISIBLE else View.GONE
}
}
Trying to add item range changed to your code works like this for me
fun updateUsersList(users: List<User>) {
val diffCallback = UserListDiffCallback(this.mUsers, users)
val diffResult = DiffUtil.calculateDiff(diffCallback)
this.mUsers.clear()
this.mUsers.addAll(users)
diffResult.dispatchUpdatesTo(this)
notifyItemRangeChanged(0, users.size)
}
I'm pretty new to kotlin, and I feel pretty much overwhelmed by it. I'd like to ask - how I can display any data from MutableLiveData? I've tried to Log it, but it doesn't seem to work. I've already added the internet permission to the manifest. Here's the code:
ApiServices
interface ApiServices {
#GET("/fixer/latest/")
fun getRatesData(
#Query("base") base: String,
#Query("apikey") apikey: String
): Call<CurrencyModel>
companion object {
private const val url = "https://api.apilayer.com/"
var apiServices: ApiServices? = null
fun getInstance(): ApiServices {
if (apiServices == null) {
val retrofit = Retrofit.Builder()
.baseUrl(url)
.addConverterFactory(GsonConverterFactory.create())
.build()
apiServices = retrofit.create(ApiServices::class.java)
}
return apiServices!!
}
}
}
Repository
class CurrencyRepository constructor(private val apiServices: ApiServices) {
fun getLatestRates() = apiServices.getRatesData("EUR", "API_KEY");
}
ViewModel
class CurrencyViewModel constructor(private val currencyRepository: CurrencyRepository) :
ViewModel() {
val currencyRatesList = MutableLiveData<CurrencyModel>()
val errorMessage = MutableLiveData<String>()
fun getLatestRates() {
val response = currencyRepository.getLatestRates();
response.enqueue(object : retrofit2.Callback<CurrencyModel> {
override fun onResponse(
call: retrofit2.Call<CurrencyModel>,
response: Response<CurrencyModel>
) {
currencyRatesList.postValue(response.body())
}
override fun onFailure(call: retrofit2.Call<CurrencyModel>, t: Throwable) {
errorMessage.postValue(t.message)
}
})
}
}
FactoryViewModel
class CurrencyViewModelFactory constructor(private val repository: CurrencyRepository) :
ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return if (modelClass.isAssignableFrom(CurrencyViewModel::class.java)) {
CurrencyViewModel(this.repository) as T
}else{
throw IllegalArgumentException("Couldn't found ViewModel")
}
}
}
MainActivity
class MainActivity : AppCompatActivity() {
private val retrofitService = ApiServices.getInstance()
lateinit var viewModel: CurrencyViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel = ViewModelProvider(this, CurrencyViewModelFactory(CurrencyRepository(retrofitService)))
.get(CurrencyViewModel::class.java)
viewModel.currencyRatesList.observe(this, Observer {
Log.d(TAG, "onCreate: $it")
})
viewModel.errorMessage.observe(this, Observer {
viewModel.getLatestRates()
})
}
}
You never call viewModel.getLatestRates() in your onCreate() to fetch an initial value for your LiveData, so it never emits anything to observe. The only place you have called it is inside your error listener, which won't be called until a fetch returns an error.
Side note, I recommend naming the function "fetchLatestRates()". By convention, "get" implies that the function returns what it's getting immediately rather than passing it to a LiveData later when its ready.
And a tip. Instead of this:
class MainActivity : AppCompatActivity() {
lateinit var viewModel: CurrencyViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// ...
viewModel = ViewModelProvider(this, CurrencyViewModelFactory(CurrencyRepository(retrofitService)))
.get(CurrencyViewModel::class.java)
//...
}
}
You can do this for the same result:
class MainActivity : AppCompatActivity() {
val viewModel: CurrencyViewModel by viewModels(CurrencyViewModelFactory(CurrencyRepository(retrofitService)))
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// ...
}
}
class SummonerInfoActivity: AppCompatActivity() {
private lateinit var participantAdapter: ParticipantAdapter
private val recycler: RecyclerView by lazy {
findViewById(R.id.RecyclerView)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val Summoner = intent.getParcelableExtra<SummonerDTO>("SummonerDTO")
Log.e("Info","${Summoner}")
val retrofit2 = Retrofit.Builder()
.baseUrl("https://asia.api.riotgames.com")
.addConverterFactory(GsonConverterFactory.create())
.build()
lolAPIForMatch = retrofit2.create(lolAPIService::class.java)
setContentView(R.layout.activity_summoner_info)
participantAdapter = ParticipantAdapter()
recycler.adapter = participantAdapter
recycler.layoutManager = LinearLayoutManager(this#SummonerInfoActivity)
getMatchIdBypuuid(Summoner?.puuId.orEmpty(),Summoner?.summonerName.orEmpty())
}
private fun getMatchIdBypuuid(puuid: String,summonerName: String){
lolAPIForMatch.getMatchIdBypuuid(puuid, 0,20, API_KEY)
.enqueue(object : Callback<List<String>> {
override fun onResponse(
call: Call<List<String>>,
response: Response<List<String>>
) {
if(response.isSuccessful.not()){
return
}
response.body()?.let {
it.forEach {
searchMatchInfoByMatchID(it,summonerName)
}
}
}
override fun onFailure(call: Call<List<String>>, t: Throwable) {
}
})
}
private fun searchMatchInfoByMatchID(matchId: String,summonerName: String){
lolAPIForMatch.getMatchInfoByMatchID(matchId, API_KEY)
.enqueue(object : Callback<MatchDTO>{
override fun onResponse(call: Call<MatchDTO>, response: Response<MatchDTO>) {
if(response.isSuccessful.not()){
return
}
response.body()?.let {
it.info.participants.filter {
it.summonerName == "${summonerName}"
}.forEach {
participantAdapter.participants.add(it)
}
}
}
override fun onFailure(call: Call<MatchDTO>, t: Throwable) {
}
})
}
}
class ParticipantAdapter: RecyclerView.Adapter<ParticipantAdapter.ViewHolder>() {
var participants = mutableListOf<ParticipantDTO>()
inner class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView){
fun bind(participant: ParticipantDTO){
itemView.findViewById<TextView>(R.id.kill).text = participant.kills.toString()
itemView.findViewById<TextView>(R.id.death).text = participant.deaths.toString()
itemView.findViewById<TextView>(R.id.assist).text = participant.assists.toString()
}
}
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): ParticipantAdapter.ViewHolder {
val inflater = LayoutInflater.from(parent.context)
return ViewHolder(inflater.inflate(R.layout.item_match, parent, false))
}
override fun onBindViewHolder(holder: ParticipantAdapter.ViewHolder, position: Int) {
holder.bind(participants[position])
}
override fun getItemCount(): Int {
return participants.size
}
}
participantAdapter.participants.add(it)
The command is searchMatchInfoByMatchID
In the function, you can check that the value is entered correctly, but
If you check in the onCreate function, the value is not assigned.
I want to use the recycler view by properly assigning a value
I tried to solve this problem, but my skills were not enough.
I desperately need the advice of seniors.
I would really appreciate it if you could show me a code sample if possible
i am new to kotlin android i am trying to use room with flow my code as follows
my entity as follows
#Entity(tableName = "user_data")
data class User (
#PrimaryKey(autoGenerate = true)
val id:Int,
val firstName:String,
val lastName:String,
val age:Int)
my database class as follows
#Database(entities = [User::class],version = 1,exportSchema = false)
abstract class UserDatabase :RoomDatabase(){
abstract fun userDao():UserDao
companion object{
#Volatile
private var INSTANCE:UserDatabase?=null
fun getDataBaseInstance(context:Context):UserDatabase
{
var tempInstance= INSTANCE
if(tempInstance!=null)
{
return tempInstance
}
synchronized(this)
{
val instance=Room.databaseBuilder(context.applicationContext,UserDatabase::class.java,"user_database")
.build()
INSTANCE=instance
return instance
}
}
}
}
my dao as follows
#Dao
interface UserDao {
#Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun addUser(user:User) :Long
#Query("SELECT * from user_data")
fun fetchUserData(): Flow<List<User>>
}
my repository as follows
class UserRepository(private val userDao:UserDao) {
suspend fun addUser(user:User){
val rowid:Long=userDao.addUser(user)
}
fun readAllUserData():Flow<List<User>>
{
val temp=userDao.fetchUserData()
return temp
}
}
my view model as follows
class UserViewModel(application: Context) :ViewModel() {
private lateinit var _readAllData:MutableLiveData<List<User>>
private var repository: UserRepository
private var _firstName:String="Akshay"
val firstName:String get()=_firstName
private var _lastName:String=""
val lastName:String get()=_lastName
private var _age:String = ""
val age:String get()=_age
val readAllData:LiveData<List<User>> get()=_readAllData
init {
val userDao= UserDatabase.getDataBaseInstance(application).userDao()
repository=UserRepository(userDao)
}
fun setFirstName(name:String)
{
_firstName=name
}
fun setLastName(name:String)
{
_lastName=name
}
fun setAge(name:String)
{
_age=name
}
fun addUserToDB()
{
viewModelScope.launch(Dispatchers.IO) {
println("Inside addUserDB")
repository.addUser(User(0,_firstName,_lastName,_age.toInt()))
}
}
fun readDataFromUserTable():LiveData<List<User>>
{
return repository.readAllUserData().asLiveData()
}
}
class UserViewModelFactory(private val context: Context):ViewModelProvider.Factory{
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if(modelClass.isAssignableFrom(UserViewModel::class.java))
{
return UserViewModel(context)as T
}
throw IllegalArgumentException("Unknown class")
}
my fragment as follows
class ListFragment : Fragment() {
private var binding:FragmentListBinding?=null
private val sharedViewModel:UserViewModel by activityViewModels {UserViewModelFactory(requireContext())}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val fragmentBinding=FragmentListBinding.inflate(inflater,container,false);
binding=fragmentBinding
return fragmentBinding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding?.apply {
viewModel=sharedViewModel
lifecycleOwner=viewLifecycleOwner
listFragment=this#ListFragment
ToastCount()
}
}
private fun ToastCount() {
var temp=sharedViewModel.readDataFromUserTable()
Toast.makeText(requireContext(), temp.size.toString(),Toast.LENGTH_SHORT).show()
}
fun addNewUser()
{ findNavController().navigate(R.id.action_listFragment_to_addFragment)
}
}
I can add data I see data in my database inspector but I can not read data it always return me null in my ViewModel ... I have added all necessary dependencies somebody please guild me what is it I am doing wrong? Thanks in advance
I don't know why it's says me that "java.lang.IndexOutOfBoundsException: Index: 0, Size: 0"
I've been searching for this since 2 hours, and i don't know why there is this problem now, can you help me ? I want to pass data from apiRest into another activity with intent and putExtra/getExtra (I will do with firebase later, more simple and more easy way).
Main Activity
package com.mehdi.myapplication
class MainActivity : AppCompatActivity(), ListAdapter.OnItemClickListener {
lateinit var userAdapter: ListAdapter
var lm = LinearLayoutManager(this)
private val users: MutableList<Results> = mutableListOf()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initView()
getClassApiRet()
}
override fun onItemClick(position: Int) {
Toast.makeText(this, position.toString(), Toast.LENGTH_LONG).show()
val intent = Intent(this, DetailPageActivity::class.java)
intent.putExtra("email", users[position].email)
startActivity(intent)
}
fun initView() {
UserRecycleView.layoutManager = lm
userAdapter = ListAdapter(this, this)
UserRecycleView.adapter = userAdapter
}
fun getClassApiRet() {
val retro = Instance().getRetroInstance().create(ClassApiRest::class.java)
retro.getData().enqueue(object : Callback<ClassApi> {
override fun onResponse(call: Call<ClassApi>, response: Response<ClassApi>) {
val users = response.body()
runOnUiThread {
userAdapter.setUsers(users?.results!!)
}
}
override fun onFailure(call: Call<ClassApi>, t: Throwable) {
Log.e("Failed", t.message.toString())
}
})
}
}
UserInfo
class ClassApi {
#SerializedName("results")
#Expose
val results: List<Results>? = null
}
class Results {
#SerializedName("name")
#Expose
val name: Name? = null
#SerializedName("email")
#Expose
val email: String? = null
#SerializedName("login")
#Expose
val login: Login? = null
#SerializedName("picture")
#Expose
val picture: Picture? = null
}
class Name {
#SerializedName("title")
#Expose
val title: String? = null
#SerializedName("first")
#Expose
val first: String? = null
#SerializedName("last")
#Expose
val last: String? = null
}
class Login {
#SerializedName("username")
#Expose
val username: String? = null
}
class Picture {
#SerializedName("medium")
#Expose
val medium: String? = null
#SerializedName("large")
#Expose
val large: String? = null
}
ListAdapter
class ListAdapter(val context: Context, val listener: OnItemClickListener): RecyclerView.Adapter<ListAdapter.UsersViewHolder>() {
private val users: MutableList<Results> = mutableListOf()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ListAdapter.UsersViewHolder {
return UsersViewHolder(LayoutInflater.from(context).inflate(R.layout.row_user_list, parent, false))
}
override fun onBindViewHolder(holder: ListAdapter.UsersViewHolder, position: Int) {
holder.bindModel(users[position])
}
override fun getItemCount(): Int {
return users.size
}
fun setUsers(results: List<Results>) {
users.clear()
users.addAll(results)
notifyDataSetChanged()
}
inner class UsersViewHolder(item: View) : RecyclerView.ViewHolder(item), View.OnClickListener {
val UserImage: CircleImageView = item.findViewById(R.id.UserImage)
val UserName: TextView = item.findViewById(R.id.UserName)
val UserPseudo: TextView = item.findViewById(R.id.UserPseudo)
init {
itemView.setOnClickListener(this)
}
fun bindModel(b: Results) {
UserName.text = b.name?.first
UserPseudo.text = b.login?.username
val url = b.picture?.medium
Glide.with(UserImage)
.load(url)
.placeholder(R.drawable.ic_launcher_background)
.error(R.drawable.ic_launcher_background)
.fallback(R.drawable.ic_launcher_foreground)
.into(UserImage)
}
override fun onClick(v: View?) {
val position = adapterPosition
if (position != RecyclerView.NO_POSITION) {
listener.onItemClick(position)
}
}
}
interface OnItemClickListener {
fun onItemClick(position: Int)
}
}
Edit !!!
I think the probleme is with the position and the interface, he can't get data.
ListAdapter
override fun onClick(v: View?) {
val position = adapterPosition
if (position != RecyclerView.NO_POSITION) {
listener.onItemClick(position)
}
}
}
interface OnItemClickListener {
fun onItemClick(position: Int)
}
Main Activity
override fun onItemClick(position: Int) {
Toast.makeText(this, users[position].email.toString(), Toast.LENGTH_LONG).show()
val intent = Intent(this, DetailPageActivity::class.java)
//intent.putExtra("lolipop", users[position].toString())
startActivity(intent)
}
Your response listener is passing the list of users to the ListAdapter, but it doesn't do anything to the MainActivity's users property, so that list remains empty. Then your item click listener tries to access the user by index in that empty list.
I would remove the users property from the Activity since there is no practical use for it. In your ListAdapter, change the onItemClick function of the OnItemClickListener to pass the actual User instead of the User's position in the data. You can get the actual User using the adapterPosition from within the adapter, since it owns the users list.
Edit: I'm suggesting to change your listener function signature to return an item directly. There's no reason the Activity should have to find the item in the collection when the Adapter can just provide it directly.
// ListAdapter:
override fun onClick(v: View?) {
listener.onItemClick(users[adapterPosition])
}
interface OnItemClickListener {
fun onItemClick(results: Results)
}