I am having an issue with reloading data in RecyclerView after the data is updated with a separate URL call. The FristFragment calls LoadDataFromUrl and displays the data in the Recycler View. Each Recycler's view item has a button with OnSetClickListener that calls another URL to update item's name data. After the user updates the item's name and a successful response is received, then the original myResponse data is updated with the new item's name. When the data is updated, I need to reload data in the Recycer View` but I can not figure it out. I spent two days trying to fix it but I can't get it running. Any help would be greatly appreciated!
Data Model:
DataModel.kt
class MyResponse (var Status: String = "", val items: List<Items> = emptyList())
class Items(var ItemId: String = "", var ItemName: String = "")
Code for Recycler View:
Adapter.kt
var recyclerView: RecyclerView? = null
class MainAdapter(val myResponse: MyResponse): RecyclerView.Adapter<CustomViewHolder>(){
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomViewHolder {
val layoutInflater = LayoutInflater.from(parent?.context)
val cellForRow = layoutInflater.inflate(R.layout.my_custom_cell, parent,false)
return CustomViewHolder(cellForRow)
}
override fun onBindViewHolder(holder: CustomViewHolder, position: Int) {
val item = myResponse.items.get(position)
//there is a button for each item in the list that will call a function ButtonPressed from Frist Fragment
holder.view.button.setOnClickListener {
val firstFragment = FirstFragment()
firstFragment.ButtonPressed(item.ItemId,item.ItemName, position)
}
holder.view.textView_ItemID = item.itemId
holder.view.textView_Item_Name = item.itemName
}
class CustomViewHolder(val view: View, var Items: Item? = null): RecyclerView.ViewHolder(view){
}
}
Then I have a fragment Fragment.kt
FirstFragment.kt
class FirstFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val rootView = inflater.inflate(R.layout.first_fragment, container, false)
return rootView
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
LoadDataFromUrl()
recyclerView.layoutManager = LinearLayoutManager(this.context,
LinearLayoutManager.HORIZONTAL, false)
}
//this function loads data on create view
fun LoadDataFromUrl(){
val url = "some_url"
val payload = "some_data_to_send"
val requestBody = payload.toRequestBody()
val request = Request.Builder()
.method("POST",requestBody)
.url(url)
.build()
val client = OkHttpClient()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
println("error")
}
override fun onResponse(call: Call, response: Response) {
val body = response?.body?.string()
val gson = GsonBuilder().create()
myResponse = gson.fromJson(body, MyResponse::class.java)
activity?.runOnUiThread {
if (myResponse.Status== "200"){
recyclerView.adapter = MainAdapter(myResponse)
}
}
}
})
}
fun ButtonPressed(itemId: String, itemName: String, position: Int){
val url = "some_another_url"
val payload = "some_another_data_to_send"
val requestBody = payload.toRequestBody()
val request = Request.Builder()
.method("POST",requestBody)
.url(url)
.build()
val client = OkHttpClient()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
println("error")
}
override fun onResponse(call: Call, response: Response) {
val body = response?.body?.string()
val gson = GsonBuilder().create()
val buttomPressedResponse = gson.fromJson(body, ButtonPressedResponse::class.java)
if (buttonPressedResponse.Status== "200") {
myResponse.response[position].Status = buttomPressedResponse.Status //will update existing object myResponse with a new item's name
//this is where I have a problem
recyclerView.adapter.notifyDataSetChanged()
}
}
}
}
I tried the following changes but I still get an error.
//I get this error: Fatal Exception: OkHttp Dispatcher Process. recyclerView must not be null. Then the app crashes and the view reloads. If I clcik the Button again I get an error saying: RecyclerView: No adapter atatched. Skipping layout.
activity?.runOnUiThread {
myResponse.response[position].Status = checkInOutResponse.Status //will update existing object myResponse with updated data
recyclerView.adapter?.notifyDataSetChanged()
}
I also tried to run on it runOnUiTHread but nothing happens with this code
activity?.runOnUiThread {
myResponse.response[position].Status = checkInOutResponse.Status //will update existing object myResponse with updated data
recyclerView.adapter?.notifyDataSetChanged()
}
Create var myResponse: MyResponse variable in Adapter
Adapter.kt
var recyclerView: RecyclerView? = null
class MainAdapter(val myResponseInit: MyResponse): RecyclerView.Adapter<CustomViewHolder>(){
var myResponse: MyResponse
myResponse = myResponseInit
fun submitMyResponse(data: MyResponse) {
myResponse = data
}
//Rest of the code onCreateViewHolder etc.
}
Call submitMyResponse() function and notifyDataSetChanged() on adapter everytime you receive response.
Related
I was trying to build a RecyclerView using a response from Retrofit. But, I ran into an issue that my Recycler turns up empty white while my log shows that I have data in my ArrayList from the network response. (I do not want to set up an MVVM yet until I get comfortable with Kotlin.)
PlaylistRecyclerAdapter
class PlaylistRecyclerAdapter (private val playListNames: Array<String>) :
RecyclerView.Adapter<PlaylistRecyclerAdapter.PlayListViewHolder>() {
// Describes an item view and its place within the RecyclerView
class PlayListViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val playlistTextView: TextView = itemView.findViewById(R.id.playlist_name_text)
fun bind(word: String) {
playlistTextView.text = word
}
}
// Returns a new ViewHolder
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PlayListViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.playlist_name_item, parent, false)
return PlayListViewHolder(view)
}
// Returns size of data list
override fun getItemCount(): Int {
return playListNames.size
}
// Displays data at a certain position
override fun onBindViewHolder(holder: PlayListViewHolder, position: Int) {
holder.bind(playListNames[position])
}
}
MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val recyclerView: RecyclerView = findViewById(R.id.recyclerView)
val templist = getPlaylistItems()
//Log.d("RESPONSE", "onCreate: "+templist.get(0).toString())
recyclerView.adapter = PlaylistRecyclerAdapter(templist.toTypedArray())
recyclerView.adapter?.notifyDataSetChanged()
}
private fun getPlaylistItems(): ArrayList<String> {
var playlisttitles = ArrayList<String>()
var BASE_URL = "https://flicastdemo.s3.amazonaws.com/jwplayer/"
val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
val service = retrofit.create(HomeWebService::class.java)
val call = service.getHomeContent()
var home = HomeRoot()
call.enqueue(object : Callback<HomeRoot> {
override fun onResponse(call: Call<HomeRoot>, response: Response<HomeRoot>) {
if (response.code() == 200) {
home = response.body()
if(!home.equals(null))
{
//Log.e("HOME", "val: " + home.toString())
for (i in 0 until home.content.size){
val BASE_URL = "https://cdn.jwplayer.com/v2/"
val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
val service = retrofit.create(PlaylistWebService::class.java)
val call = service.getPlayListItem(home.content.get(i).playlistId) //"1QhdrFVq"
call.enqueue(object : Callback<PlaylistRoot> {
override fun onResponse(call: Call<PlaylistRoot>, response: Response<PlaylistRoot>) {
if (response.code() == 200) {
var playlistinfo : PlaylistRoot = response.body();
playlisttitles.add(playlistinfo.title)
Log.e("PlaylistTitle!", "onResponseTitle: "+playlistinfo.title)
}
}
override fun onFailure(call: Call<PlaylistRoot>, t: Throwable) {
Log.d("NO!NO!NO!", "onResponse: "+"NO!")
playlisttitles.add("No Playlist")
}
})
}
}
}
}
override fun onFailure(call: Call<HomeRoot>, t: Throwable) {
Log.d("NO!NO!NO!", "onResponse: "+"NO!")
}
})
return playlisttitles
}
}
Retrofit returns data in a background thread, so the callback to onResponse() is asynchronous to the UI, i.e. it takes some time until the data comes in; and therefore the getPlaylistItems() method will be returned before the retrofit data is up. And therefore it returns an empty list in val templist = getPlaylistItems().
To fix, this you can create a listener interface, or just build-up the RecyclerView within the onResponse callback:
override fun onResponse(call: Call<PlaylistRoot>, response: Response<PlaylistRoot>) {
if (response.code() == 200) {
var playlistinfo : PlaylistRoot = response.body();
playlisttitles.add(playlistinfo.title)
Log.e("PlaylistTitle!", "onResponseTitle: "+playlistinfo.title)
recyclerView.adapter = PlaylistRecyclerAdapter(playlisttitles.toTypedArray())
recyclerView.adapter?.notifyDataSetChanged()
}
}
I've been trying to solve this problem for over 3 hours. Everything seems just fine on the Logcat and Debug mode. I'm fetching the List without any problem, Fragment is reading the MutableLiveData successfully. Only the notifyDataSetChanged() function is not working and also it doesn't give any error etc. If I send an ArrayList manually then it works but if it goes inside Retrofit and DisposableSingleObserver then even the manual list doesn't work.
I have tried every way that I could have found on the internet. I've looked for more than 20 different solution none of them have worked.
API - HoroscopeAPI.kt
interface HoroscopeAPI {
#GET("astraios/horoscopeList.json")
fun getHoroscope(): Single<List<Horoscope>>
}
Service - HoroscopeAPIService.kt
class HoroscopeAPIService {
private val BASE_URL = "https://wiuma.co"
private val api = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build()
.create(HoroscopeAPI::class.java)
fun getData(): Single<List<Horoscope>> {
return api.getHoroscope()
}
}
ViewModel - HoroscopeViewModel.kt
class HoroscopeViewModel : ViewModel() {
private val horoscopeApiService = HoroscopeAPIService()
private val disposable = CompositeDisposable()
val horoscopeList = MutableLiveData<List<Horoscope>>()
fun getDataFromAPI() {
loadingStatus.value = true
disposable.add(
horoscopeApiService.getData()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(object : DisposableSingleObserver<List<Horoscope>>() {
override fun onSuccess(t: List<Horoscope>) {
horoscopeList.value = t
}
override fun onError(e: Throwable) {
e.printStackTrace()
}
})
)
}
}
Fragment - Horoscope.kt
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
horoscopeViewModel =
ViewModelProvider(this).get(HoroscopeViewModel::class.java)
val root = inflater.inflate(R.layout.fragment_horoscope, container, false)
horoscopeViewModel.getDataFromAPI()
horoscopeRecyclerView = root.findViewById(R.id.horoscopeRecyclerView)
horoscopeRecyclerView.layoutManager = LinearLayoutManager(context)
horoscopeRecyclerView.adapter = recyclerViewAdapter
observeData()
return root
}
fun observeData() {
horoscopeViewModel.horoscopeList.observe(viewLifecycleOwner, Observer { horoscope ->
horoscope?.let {
recyclerViewAdapter.updateList(horoscope)
}
})}
**Adapter - HoroscopeRecyclerAdapter.kt **
class HoroscopeRecyclerAdapter(val horoscopeList: ArrayList<Horoscope>) :
RecyclerView.Adapter<HoroscopeRecyclerAdapter.HoroscopeViewHolder>() {
class HoroscopeViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HoroscopeViewHolder {
val inflater = LayoutInflater.from(parent.context)
val view = inflater.inflate(R.layout.horoscope_recycler_row, parent, false)
return HoroscopeViewHolder(view)
}
#SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: HoroscopeViewHolder, position: Int) {
holder.itemView.horoscopeName.text = horoscopeList.get(position).nameHoroscope
holder.itemView.horoscopeDates.text =
horoscopeList.get(position).startDate + " " + horoscopeList.get(position).endDate
//TODO Gorsel baglantisi eklenecek.
holder.itemView.setOnClickListener {
val action =
HoroscopeFragmentDirections.actionNavigationHoroscopeToNavigationHoroscopeDetails(0)
Navigation.findNavController(it).navigate(action)
}
}
override fun getItemCount(): Int {
return horoscopeList.size
}
fun updateList(newHoroscopeList: List<Horoscope>) {
horoscopeList.clear()
horoscopeList.addAll(newHoroscopeList)
notifyDataSetChanged()
}}
I ran your project from Github. notifyDataSetChanged() seems to be working fine. The reason why the list items aren't showing up is that the visibility of the RecyclerView is set to GONE. It needs to be set back to VISIBLE when the results arrive:
fun observeData() {
horoscopeViewModel.horoscopeList.observe(viewLifecycleOwner, Observer { horoscope ->
horoscope?.let {
errorText.visibility = View.GONE
progressBar.visibility = View.GONE
horoscopeRecyclerView.visibility = View.VISIBLE
recyclerViewAdapter.updateList(it)
}
})
I have API data which ID's are uuid (strings) and not (integer) and when I want to get those ids in my adapter it says
Type mismatch.
Required:
Int
Found:
String?
Sample of API items
{
"id":"0ade1bfb-6d02-4a1f-9cd4-dc88fa8aadbd",
"name":"ABC",
"photo":null // if not null, will be full URL of image (https://example.com/img/abc.jpg)
}
Code
Adapter (commented)
class ServicesAdapter(private val serviceList: List<Service>) : RecyclerView.Adapter<ServicesAdapter.ServiceViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ServiceViewHolder {
val imageView =
LayoutInflater.from(parent.context).inflate(R.layout.service_item, parent, false)
return ServiceViewHolder(imageView)
}
override fun onBindViewHolder(holder: ServiceViewHolder, position: Int) {
val currentItem = serviceList[position]
holder.imageView.setImageResource(currentItem.photo) <-- error line
holder.textView.text = currentItem.name
}
override fun getItemCount() = serviceList.size
class ServiceViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val imageView: ImageView = itemView.imageView
val textView: TextView = itemView.textView2
}
}
Activity
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
run("api_url")
}
fun run(url: String) {
val request = Request.Builder()
.url(url)
.build()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {}
override fun onResponse(call: Call, response: Response) {
val list: ArrayList<Service> = ArrayList()
getServices(response.body()!!.string(), list)
recycler.layoutManager = LinearLayoutManager(this#MainActivity)
recycler.adapter = ServicesAdapter(list)
}
})
}
fun getServices(response: String, list: ArrayList<Service>) {
var jsonObject = JSONObject(response)
val jsonArray = jsonObject.getJSONArray("data")
for (i in 0 until jsonArray.length()) {
val jsonObject1 = jsonArray.getJSONObject(i)
var listingObject = Service(
jsonObject1.getString("id"),
jsonObject1.getString("name"),
jsonObject1.getString("photo")
)
list.add(listingObject)
}
}
Class
class Service (val id: String?, val name: String?, val photo: String?) {
}
Any idea?
Add the following lines of code in your OnBindViewHolder to load images from the URL
currentItem.photo?.apply{
Glide.with(holder.imageView.context)
.load(this)
.into(holder.imageView)
}
holder.imageView.setImageResource(currentItem.photo)
this method requires int value, which you are providing null(treated as null string)
try replacing null as blank while parsing json data
or you can use this
public static boolean isBlankOrNull(String value)
{
return (value == null || value.equals("") || value.equals("null") || value.trim().equals(""));
}
like this
holder.imageView.setImageResource(isBlankOrNull(currentItem.photo) ? 0 : currentItem.photo)
Just set image when you are getting it in response i.e. its value is not empty or null.Also you need to use any library to set image to imageview for example You can use Square's Picasso and can update your code as below
if(!TextUtils.isEmpty(currentItem.photo))
Picasso
.get()
.load(currentItem.photo)
.into(holder.imageView)
I try to get data from json: https://github.com/rolling-scopes-school/rs.android.task.6/blob/master/data/data.json
Service to prepare retrofit to make the call:
object ServiceBuilder {
private val client = OkHttpClient.Builder().build()
var gson = GsonBuilder()
.setLenient()
.create()
private val retrofit = Retrofit.Builder()
.baseUrl("https://github.com/rolling-scopes-school/rs.android.task.6/blob/master/data/")
.addConverterFactory(GsonConverterFactory.create(gson))
.client(client)
.build()
fun<T> buildService(service: Class<T>): T{
return retrofit.create(service)
}
}
The Interface:
interface ApiInterface {
#GET("/data.json")
fun getItems(): Call <List<Item>>
}
MainActivity code:
val request = ServiceBuilder.buildService(ApiInterface::class.java)
val call = request.getItems()
call.enqueue(object : Callback<List<Item>> {
override fun onFailure(call: Call<List<Item>>, t: Throwable) {
Toast.makeText(this#MainActivity, "${t.message}", Toast.LENGTH_LONG).show()
}
override fun onResponse(call: Call<List<Item>>, response: Response<List<Item>>) {
if (response.isSuccessful){
recyclerview.apply {
progress_bar.visibility = View.GONE
setHasFixedSize(true)
layoutManager = LinearLayoutManager(this#MainActivity)
adapter = MyAdapter(response.body()!!)
}
}
}
})
}
MyAdapter:
class MyAdapter(val items: List): RecyclerView.Adapter() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemsViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.recycler_layout, parent, false)
return ItemsViewHolder(view)
}
override fun getItemCount(): Int {
return items.size
System.out.println("items.size - " + items.size)
}
override fun onBindViewHolder(holder: ItemsViewHolder, position: Int) {
val title = items[position].title ?: ""
val description = items[position].description ?: ""
val imageurl = items[position].image.url ?: ""
holder.bind(title, description, imageurl)
}
}
class ItemsViewHolder(itemView : View): RecyclerView.ViewHolder(itemView){
private val photo: ImageView = itemView.findViewById(R.id.image_photo)
private val title_text: TextView = itemView.findViewById(R.id.title_)
private val description_text:TextView = itemView.findViewById(R.id.description)
fun bind(title: String,description:String,imageurl:String) {
Glide.with(itemView.context).load(imageurl).into(photo)
title_text.text = title
description_text.text = description
}
Dataclass Item:
data class Item(
val description: String,
val duration: Duration,
val enclosure: Enclosure,
val group: Group,
val guid: Guid,
val image: ImageX,
val link: String,
val pubDate: String,
val title: String
)
I run app and no such data in screen :-). What's wrong? Can you help me?
The problem seems to be lying here. you are getting the response and you are creating a new adapter each time, but not assigning the new adapter to the recyclerview i guess.
override fun onResponse(call: Call<List<Item>>, response: Response<List<Item>>) {
if (response.isSuccessful){
recyclerview.apply {
progress_bar.visibility = View.GONE
setHasFixedSize(true)
layoutManager = LinearLayoutManager(this#MainActivity)
adapter = MyAdapter(response.body()!!)
//add this code to assign the adapter and notify the adapter
recyclerview.adapter = adapter
adapter.notifyDataSetChanged()
}
}
}
note: it would be better if you create your adapter in onCreate() and assign the adapter to RecyclerView there only, and now when you call the github api to get the details just update the data in the adapter and call notifyDataSetChanged on adapter to notify about the new data changed.
Edit: It seems there is issue with the api endpoint also you won't get the json data by hitting that url directly. you need to convert this to raw. something like this.
https://raw.githubusercontent.com/rolling-scopes-school/rs.android.task.6/master/data/data.json
Also the model which you have mentioned won't be mapped by json adapter because the response is in a different format so here change the model to something like below
data class GitResponse(val channel: Channel)
data class Channel(val item: List<Item>)
so change the retrofit with the following url param.
interface ApiInterface {
#GET
fun getItems(#Url url: String): Call<GitResponse>
}
//and where ever you are call this now pass the full url which is different from the one you have.
val url = "https://raw.githubusercontent.com/rolling-scopes-school/rs.android.task.6/master/data/data.json"
val request = ServiceBuilder.buildService(ApiInterface::class.java)
val call = request.getItems(url)
call.enqueue(object : Callback<GitResponse> {
override fun onFailure(call: Call<GitResponse>, t: Throwable) {
Toast.makeText(this#MainActivity, "${t.message}", Toast.LENGTH_LONG).show()
}
override fun onResponse(call: Call<GitResponse>, response: Response<GitResponse>) {
if (response.isSuccessful){
recyclerview.apply {
progress_bar.visibility = View.GONE
setHasFixedSize(true)
layoutManager = LinearLayoutManager(this#MainActivity)
adapter = MyAdapter(response.body()?.channel?.item ?: listOf())
//add this code to assign the adapter and notify
recyclerview.adapter = adapter
adapter.notifyDataSetChanged()
}
}
}
})
now you will get the api response, check by applying debug point.
I am implementing a RecyclerView in a fragment. The XML should be correct since I tried it with my hard-coded data, and the API call does return the correct json data from the server according to the Log in the console. The problem is that the RecyclerView adapter does not get any data from my Observable. Here is my implementation
In PostDataService interface I used Retrofit to get an Observable>
interface PostDataService {
#GET(".")
fun getPosts(
#Query(value = "offset") offset: Long = 0,
#Query(value = "limit") limit: Long = 10,
#Query(value = "subscribedOnly") subscribedOnly: Boolean = false
): Observable<List<Post>>
companion object {
val retrofit: PostDataService = Retrofit.Builder()
.baseUrl("http:aws/api/post/")
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(MoshiConverterFactory.create())
.client(client)
.build()
.create(PostDataService::class.java)
}
}
In PostListRepository, I used RxJava operators to get the LiveData
class PostListRepository {
private val postListLiveData: MutableLiveData<List<Post>> = MutableLiveData()
private val compositeDisposable: CompositeDisposable = CompositeDisposable()
fun getPostListLiveData(): MutableLiveData<List<Post>> {
val postList: MutableList<Post> = ArrayList()
val retrofitInstance = PostDataService.retrofit
val postListObservable = retrofitInstance.getPosts()
compositeDisposable.add(
postListObservable
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.flatMapIterable { it }
.subscribeWith(object : DisposableObserver<Post>() {
override fun onError(e: Throwable) {
// if some error happens in our data layer our app will not crash, we will
// get error here
}
override fun onNext(post: Post) {
postList.add(post)
}
override fun onComplete() {
postListLiveData.postValue(postList)
}
})
)
return postListLiveData
}
fun clear() {
compositeDisposable.clear()
}
}
In PostListViewModel, I passed the LiveData from the repository into this ViewModel.
class PostListViewModel : ViewModel() {
private var postListRepository: PostListRepository = PostListRepository()
fun getPostList(): MutableLiveData<List<Post>> {
return postListRepository.getPostListLiveData()
}
fun clear() {
postListRepository.clear()
}
}
Here is the Fragment that contains the RecyclerView. I think the .oberserve function in getPostList() is not called since I tried Log it but got nothing.
class PostListFragment : Fragment() {
private lateinit var recyclerView: RecyclerView
private lateinit var swipeLayout: SwipeRefreshLayout
private lateinit var postListViewModel: PostListViewModel
private val postListAdapter = PostRecyclerViewAdapter()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val rootView = inflater.inflate(R.layout.view_post_list, container, false)
recyclerView = rootView.findViewById(R.id.postRecyclerView)
recyclerView.apply {
setHasFixedSize(true)
addItemDecoration(VerticalSpaceItemDecoration(36))
layoutManager = LinearLayoutManager(context)
adapter = postListAdapter
}
postListViewModel = ViewModelProviders.of(this).get(PostListViewModel::class.java)
getPostList()
swipeLayout = rootView.findViewById(R.id.swipeLayout)
swipeLayout.setColorSchemeResources(R.color.colorPrimary)
swipeLayout.setOnRefreshListener {
getPostList()
swipeLayout.isRefreshing = false
}
return rootView
}
override fun onDestroy() {
super.onDestroy()
postListViewModel.clear() // to avoid memory leak
}
private fun getPostList() {
postListViewModel.getPostList().observe(this, Observer<List<Post>> { resource ->
postListAdapter.setPostList(resource)
postListAdapter.notifyDataSetChanged()
})
}
}
Here is the adapter for the RecyclerView:
class PostRecyclerViewAdapter : RecyclerView.Adapter<PostViewHolder>() {
private var postList: List<Post> = ArrayList()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PostViewHolder {
// create a new view
val postView = PostView(parent.context)
// set the view's size, margins, paddings and layout parameters
return PostViewHolder.from(postView)
}
override fun getItemCount(): Int = postList.size
override fun onBindViewHolder(holder: PostViewHolder, position: Int) {
val curPost = postList[position]
holder.postView.apply {
setPostOwnerDisplayName(curPost.content.userDisplayedName)
setPostOwnerRole(curPost.content.role)
setPostOwnerAvatar(R.mipmap.ic_launcher_round)
setPostText(curPost.content.text)
setPostImage(curPost.content.smallMediaPaths[0])
setLikeState(curPost.liked)
setBookmarkState(curPost.bookmarked)
}
}
fun setPostList(postList: List<Post>) {
this.postList = postList
}
}
As I mentioned above, I think the .oberserve function in getPostList() in PostListFragment is not called since I tried Log it but got nothing, so there is no data passed into the RecyclerView. Can anyone help me find the reason why it's not being called, or why it's not getting the data from the ViewModel?
I wouldn't think of this is related to your issue, but your code has potential problems.
To move observe part to onActivityCreated would be better to ensure view is created.
when your fragment view is re-created, a new Observer will be added, while previous one still alive, because your Observer is anonymous. So, you have to manage the observers to prevent it.
I just found out that I forgot to catch the exception in RxJava onNext() in case to get the moshi serialization error. After getting that, I got some moshi conversion errors.
Posted it in case anyone carelessly forgot to catch the moshi error.
Thanks!