I'm trying to rewrite my program and start using Kotlin Coroutines.
That is my function to retrieve a list of products for a given group. After debugging it looks like everything is correct.
class FirebaseRepository {
private val db = FirebaseFirestore.getInstance()
private val auth = FirebaseAuth.getInstance()
fun getCurrentUserId(): String{
return auth.currentUser!!.uid
}
suspend fun getLista(): MutableLiveData<List<Produkt>> {
val result = MutableLiveData<List<Produkt>>()
val lista = mutableListOf<Produkt>()
db.collection(Constants.GROUP)
.document("xGRWy21hwQ7yuBGIJtnA")
.collection("Przedmioty")
.orderBy("dataDodaniaProduktu", Query.Direction.DESCENDING)
.get().await().forEach {
val singleProdukt = it.toObject(Produkt::class.java)
singleProdukt.produktId = it.id
lista.add(singleProdukt)
result.postValue(lista)
}
return result
}
That is my ViewModel class:
class ListaViewModel: ViewModel() {
private val repository = FirebaseRepository()
var _produkty = MutableLiveData<List<Produkt>>()
val produkty : LiveData<List<Produkt>> = _produkty
init {
viewModelScope.launch {
_produkty = repository.getLista()
}
}
And finally in my fragment I'm trying to observe live data but looks like nothing is being passed to my adapter. What am I doing wrong?
class ListaFragment : Fragment(), ListaAdapter.OnItemClickListener {
private var _binding: FragmentListaBinding? = null
private val binding get() = _binding!!
private lateinit var recyclerView : RecyclerView
private lateinit var listAdapter : ListaAdapter
private val listaViewModel by viewModels<ListaViewModel>()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
_binding = FragmentListaBinding.inflate(inflater, container, false)
recyclerView = binding.recyclerView
listAdapter = ListaAdapter(emptyList(), this)
recyclerView.adapter = listAdapter // Zapobiega "No adapter attached; skipping layout"
recyclerView.layoutManager = LinearLayoutManager(requireActivity())
recyclerView.setHasFixedSize(true)
listaViewModel.produkty.observe(viewLifecycleOwner, Observer {
listAdapter = ListaAdapter(it, this)
}
return binding.root
}
Try replacing this:
val produkty : LiveData<List<Produkt>> = _produkty
with this
val produkty : LiveData<List<Produkt>> get() = _produkty
This way you'll have "getter" rather than "initializer". Initializer will compute its value once (to the empty live data) and after you reassign that var it won't change the value of your val.
The problem in your code lies in the fact that you're creating a new instance of your ListaAdapter class inside the observe() method, without notifying the adapter about the changes. That's the reason why you're getting no results in the adapter. To solve this, simply create a method inside your adapter class:
fun setProduktList(produktList: List<Produkt>) {
this.produktList = produktList
notifyDataSetChanged()
}
Then inside your observe() method, use the following line of code:
listaViewModel.produkty.observe(viewLifecycleOwner, Observer {
//listAdapter = ListaAdapter(it, this) //Removed
listAdapter.setProduktList(it) 👈
}
Related
I have a simple Android Kotlin app that i am working on which involves multiple fragments communicating with one viewModel. The problem is the LiveData observer in my first fragment will not update every time the list changes in my view model. Can anyone explain where i might be going wrong?
Here is my fragment:
class ShoeDetailsFragment : Fragment() {
lateinit var binding: FragmentShoeDetailsBinding
private val viewModel: ShoeViewModel by activityViewModels()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View {
// Inflate the layout for this fragment
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_shoe_details, container, false)
setClickListeners()
watchForChanges()
return binding.root
}
private fun watchForChanges(){
viewModel.shoeList.observe(viewLifecycleOwner) { list ->
Log.i("Contents of list: ", list.toString())
binding.viewModelTestEditText.setText(list.toString())
}
}
private fun createShoeFromInputs(): Shoe {
val shoeNameString = binding.shoeNameEditText.text.toString()
val shoeColourString = binding.shoeColourEditText.text.toString()
val shoeMakerString = binding.shoeMakerEditText.text.toString()
val shoeSizeString = binding.shoeSizeEditText.text.toString()
return Shoe(shoeNameString, shoeColourString, shoeMakerString, shoeSizeString)
}
private fun setClickListeners(){
binding.saveButtonShoeDetails.setOnClickListener{
saveShoeToList(createShoeFromInputs())
}
binding.cancelButtonShoeDetails.setOnClickListener{
viewModel.removeShoeFromShoeList()
}
}
private fun saveShoeToList(shoe: Shoe){
if (validateFields()){
viewModel.addShoeToShoeList(shoe)
}
}
private fun validateFields(): Boolean{
return if (binding.shoeNameEditText.text.isEmpty()
|| binding.shoeColourEditText.text.isEmpty()
|| binding.shoeMakerEditText.text.isEmpty()
|| binding.shoeSizeEditText.text.isEmpty()){
Toast.makeText(requireContext(), "Please complete all fields", Toast.LENGTH_SHORT).show()
false
} else {
true
}
}
}
And here is my viewModel:
class ShoeViewModel: ViewModel() {
private val _shoeList = MutableLiveData<MutableList<Shoe>>()
val shoeList: LiveData<MutableList<Shoe>> get () =_shoeList
init {
_shoeList.value = mutableListOf()
}
fun addShoeToShoeList(shoe: Shoe){
_shoeList.value!!.add(shoe)
Log.i("Contents of list in view model: ", _shoeList.value!!.size.toString())
}
fun removeShoeFromShoeList(){
_shoeList.value!!.removeAt(0)
Log.i("Contents of list in view model after cancel: ", _shoeList.value!!.size.toString())
}
}
I have checked the code over and over again but there must be something i am missing
You haven't changed the value of the LiveData. It's still pointing at the same instance of a MutableList. You modified the contents of that MutableList, but the LiveData doesn't know anything about you doing that, so it will not notify observers.
I strongly recommend that you only use read-only Lists with LiveData. Instead of mutating the list, you create a new list and set it as the new value of the LiveData.
class ShoeViewModel: ViewModel() {
private val _shoeList = MutableLiveData<List<Shoe>>()
val shoeList: LiveData<List<Shoe>> get () =_shoeList
init {
_shoeList.value = emptyList()
}
fun addShoeToShoeList(shoe: Shoe){
_shoeList.value = _shoeList.value.orEmpty() + shoe
Log.i("Contents of list in view model: ", _shoeList.value.orEmpty().size.toString())
}
fun removeShoeFromShoeList(){
_shoeList.value = _shoeList.value.orEmpty().drop(1)
Log.i("Contents of list in view model after cancel: ", _shoeList.value.orEmpty().size.toString())
}
}
Note, it is possible to use a MutableList, and then call liveData.value = liveData.value each time after you mutate the list to trigger it to notify observers. The reason I recommend you not do this is that some view classes (notably RecyclerView's ListAdapter) are "smart" and compare old and new data to determine whether they actually need to show any changes. If the old and new data are both the same instance of MutableList, it will not detect any changes so the UI will not update.
You need to call MuableLiveData.setValue() or MutableLiveData.postValue() for event to be emited.
try :
fun addShoeToShoeList(shoe: Shoe){
val currentList = _shoeList.value ?: mutableListOf()
currentList.add(shoe)
_shoeList.value = Collections.copy(currentList)
Log.i("Contents of list in view model: ", _shoeList.value!!.size.toString())
}
fun removeShoeFromShoeList(){
val currentList = _shoeList.value ?: mutableListOf()
currentList.removeAt(0)
_shoeList.value=currentList
Log.i("Contents of list in view model after cancel: ", _shoeList.value!!.size.toString())
}
So I created MVVM app in kotlin to fetch movies from TMDB api, using injections and coroutines.
My problem is that I cannot copy the list of returned movies into a new list I created or reassign any variables inside the livedata observer from the MainActivity the values of variables stays the same as they were after exit the scope.
MainActivity class:
#AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private lateinit var binding:ActivityMainBinding
private val viewModel:MoviesViewModel by lazy {
ViewModelProvider(this)[MoviesViewModel::class.java]
}
private lateinit var list: MutableList<Movies>
private var number:Int=1
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding=ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
getData()
list
number
}
private fun getData(){
list= mutableListOf()
viewModel.getData(page = "1").observe(this#MainActivity,Observer{ item ->
item?.let { res ->
when (res.status) {
Status.SUCCESS -> {
var tmpList = item.data?.body()?.results
list= tmpList!!.toMutableList()
number+=1
}
Status.ERROR -> {
res.message?.let { Log.e("Error", it) }
}}}
})}}
ViewModel class:
class MoviesViewModel #ViewModelInject constructor(var repository: MoviesRepository): ViewModel() {
fun getData(page:String)= liveData(Dispatchers.IO){
emit(Resource.loading(data = null))
try {
emit(Resource.success(data=repository.getMovies(api_key = Constants.API_KEY,
start_year=Constants.START_YEAR, end_year = Constants.END_YEAR,page = page)))
}catch (e:Exception){
emit(e.message?.let { Resource.error(message = it, data = null) })
}
}
}
As you can see I tried to change the value of number and load the list into my new list but outside the scope the values returned to be what they were before.
Very thankful for anyone who can assist.
Update:
So I tried to initialized all the items inside the success case and it worked I guess there is no other way to change the values outside the scope.
I'm trying to view my files using recycler view and MVVM, the problem is that memoryViewModel doesn't reflect the changes on memoryItemsRecyclerViewAdapter
memoryViewModel!!.mutableLiveData?.observe(this.viewLifecycleOwner) {
memoryItemsRecyclerViewAdapter.notifyDataSetChanged() //this should show the list items of the recycler view
Log.d("view model result", it.size.toString()) // log message shows the list items
}
so, the final question is why the mutableLiveData!!.postValue(mutableList) don't update the recycler view?
here is a sample of my code
the fragment
class MemoryFragment: Fragment() {
companion object{
private var rootDirectory: java.io.File? = null
var currentFolder = rootDirectory
private var filesList: MutableList<File?>? = mutableListOf()
private var memoryViewModel: MemoryViewModel? = null
}
private var sharedPreferences: SharedPreferences? = null
private lateinit var listView: RecyclerView
private lateinit var pathsRecyclerView: RecyclerView
private lateinit var searchView: androidx.appcompat.widget.SearchView
private lateinit var refreshSwipe: SwipeRefreshLayout
private lateinit var memoryItemsRecyclerViewAdapter: MemoryItemsRecyclerViewAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
sharedPreferences = androidx.preference.PreferenceManager
.getDefaultSharedPreferences(requireContext())
val memoryViewModelProvider = MemoryViewModelProvider()
memoryViewModel = ViewModelProvider(this, memoryViewModelProvider).get(MemoryViewModel::class.java)
// rootDirectory = Environment.getExternalStorageDirectory()
// currentFolder = rootDirectory
// val rootFoldersList = rootDirectory?.listFiles()?.toMutableList()
// for (item in rootFoldersList!!)
// if(item.isDirectory)
// filesList?.add(File(R.drawable.ic_folders, item, item.totalSpace))
// else
// filesList?.add(File(R.drawable.ic_file, item, item.totalSpace))
// memoryViewModel = MemoryViewModel(filesList!!)
// MemoryViewModel.lazyMemoryViewModel
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = LayoutInflater.from(requireContext()).inflate(R.layout.fragment_memory, container, false)
listView = view.findViewById(R.id.list_view)
pathsRecyclerView = view.findViewById(R.id.recycler_view)
searchView = view.findViewById(R.id.search_view)
refreshSwipe = view.findViewById(R.id.swipe_refresh)
listView.layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
memoryItemsRecyclerViewAdapter = MemoryItemsRecyclerViewAdapter(memoryViewModel!!, requireContext(), filesList!!)
listView.adapter = memoryItemsRecyclerViewAdapter
memoryViewModel!!.mutableLiveData?.observe(this.viewLifecycleOwner) {
memoryItemsRecyclerViewAdapter.notifyDataSetChanged()
Log.d("view model result", it.size.toString())
}
return view
}
}
the viewModel
class MemoryViewModel(private var mutableList: MutableList<com.example.everyentertainment.models.File?>?): ViewModel() {
private val foldersPath: MutableList<String?>? = null
private val foldersName: MutableList<String?>? = null
private var rootDirectory: File? = null
var mutableLiveData: MutableLiveData<MutableList<com.example.everyentertainment.models.File?>>? = null
companion object{
var currentFolder: File? = null
}
init {
val job = viewModelScope.launch(Dispatchers.IO) {
if (mutableLiveData == null) mutableLiveData = MutableLiveData()
if(mutableList == null) mutableList = mutableListOf()
initializeMemoryFragment()
}
job.start()
job.invokeOnCompletion {
mutableLiveData!!.postValue(mutableList)//postValue() doesn't update UI also I tried mutableLiveData.value = mutableList but it throws OnCompletionHandlerException
Log.d("mutable list size", mutableList!!.size.toString())
Log.d("mutable live data size", mutableLiveData!!.value.toString())
}
}
fun initializeMemoryFragment(){
rootDirectory = getExternalStorageDirectory()
currentFolder = rootDirectory
val filesList = rootDirectory!!.listFiles()
for(position in filesList!!.indices)
if(filesList[position].isDirectory)
mutableList!!.add(com.example.everyentertainment.models.File(R.drawable.ic_folders,
filesList[position], getFolderSize(filesList[position])))
else
mutableList!!.add(com.example.everyentertainment.models.File(R.drawable.ic_file,
filesList[position], getFolderSize(filesList[position])))
}
fun getFolderSize(file: File): Long{
if(!file.exists())
return 0
if(!file.isDirectory)
return file.length()
val dirs: MutableList<File> = LinkedList()
dirs.add(file)
var result: Long = 0
while (!dirs.isEmpty()) {
val dir = dirs.removeAt(0)
if (!dir.exists()) continue
val listFiles = dir.listFiles()
if (listFiles == null || listFiles.isEmpty()) continue
for (child in listFiles) {
result += child.length()
if (child.isDirectory) dirs.add(child)
}
}
return result
}
and finally the recycler view adapter
class MemoryItemsRecyclerViewAdapter(private val memoryViewModel: MemoryViewModel, private val context: Context, private val dataSet: MutableList<File?>?):
RecyclerView.Adapter<MemoryItemsRecyclerViewAdapter.ViewHolder>() {
/**
* Provide a reference to the type of views that you are using
* (custom ViewHolder).
*/
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val fileNameTextView: TextView = view.findViewById(R.id.name_text_view)
val sizeTextView: TextView = view.findViewById(R.id.size_text_view)
val numberOfFilesTextView: TextView = view.findViewById(R.id.number_of_files_text_view)
val dateTextView: TextView = view.findViewById(R.id.date_text_view)
val imageView: ImageView = view.findViewById(R.id.image_view)
init {
// Define click listener for the ViewHolder's View.
}
}
// Create new views (invoked by the layout manager)
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder {
// Create a new view, which defines the UI of the list item
val view = LayoutInflater.from(viewGroup.context).inflate(R.layout.memory_list_view_item, viewGroup, false)
return ViewHolder(view)
}
// Replace the contents of a view (invoked by the layout manager)
override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
// Get element from your dataset at this position and replace the
// contents of the view with that element
val file = dataSet!![position]
viewHolder.fileNameTextView.text = file!!.file.name
viewHolder.sizeTextView.text = memoryViewModel.readableFileSize(dataSet[position]!!.size)
viewHolder.dateTextView.text = memoryViewModel.getFolderDateModified(file.file)
viewHolder.numberOfFilesTextView.text = memoryViewModel.getSubFoldersQuantity(context, file.file)
if(file.file.isDirectory)
viewHolder.imageView.setImageResource(R.drawable.ic_folders)
else
viewHolder.imageView.setImageResource(R.drawable.ic_file)
}
// Return the size of your dataset (invoked by the layout manager)
override fun getItemCount() = dataSet!!.size
}
Your adapter is hardcoded to display the contents of dataSet (you fetch from it in onBindViewHolder, you reference it in getItemCount) and you never change those contents, so there's never anything to update. The RecyclerView will only ever display what you first passed in. You need to make the adapter's data updateable, I'd recommend something like this:
class MemoryItemsRecyclerViewAdapter(
private val memoryViewModel: MemoryViewModel,
private val context: Context,
private var dataSet: List<File?> = emptyList() // this is a var now, and not a mutable list
) : RecyclerView.Adapter<MemoryItemsRecyclerViewAdapter.ViewHolder>() {
// this function replaces the current data with the new set, and refreshes the UI
fun setData(newData: List<File?>) {
dataSet = newData
notifyDataSetChanged()
}
...
}
Now you have a way to update the adapter, and it takes care of updating the UI itself - it's better to have the logic (like calling notifyDataSetChanged()) in here, because really the adapter should be deciding what's changed and how to handle it.
I made dataSet a var so you can just swap the old list for the new one, and made them immutable Lists since they don't need to be mutable. I added emptyList() as a default - it doesn't need to be nullable, and you're treating it as non-null (with !! everywhere) anyway, so just make it non-null. If you really want, you can make it a mutable list and do it that way - in that case, use mutableListOf() for the empty default instead
So now you can update the adapter, you just need to do that in your observer function:
memoryViewModel!!.mutableLiveData?.observe(this.viewLifecycleOwner) { newData ->
// when a new list comes in, set it on the adapter
memoryItemsRecyclerViewAdapter.setData(newData)
Log.d("view model result", newData.size.toString())
}
and that's it. Your observer just reacts to new values as they come in (which includes any current value, when you first observe the LiveData) so you just need to do whatever with them
(Also, you're really complicating things by making everything nullable, especially when you're assuming they're all non-null when you access them anyway with !!)
Hello everyone I am faced with the following problem. I have a database Room, I have 2 tables POST_TABLE and SELL_TABLE.
Now I need to get data from two tables and add to the arraylist.
I use the observer to track when the sell items are loaded and then add it to the arraylist and same I do to the post items
I do this to show two different view types objects in recycler view.
In ViewModel everything is simple, I get access to the database, receive sell and post DAO and initialize repository.
It seems to me I'm doing wrong to make the observer in the observer in home fragment.
In the best practice I did not find a suitable answer. Maybe somebody knows how to reach data from room correctly?
From HomeFragment
private fun initObserver() {
viewModel.sellList.observe(viewLifecycleOwner, {
viewModel.addSellItemsToArray()
viewModel.postList.observe(viewLifecycleOwner, {
viewModel.addPostItemToArray()
initRecyclerView(viewModel.homeArrayList)
})
})
}
From ViewModel
private val sellRepository: SellRepository
private val postRepository: PostRepository
var sellList: LiveData<List<Sell>>
var postList: LiveData<List<Post>>
private var _homeItem = MutableLiveData<ArrayList<HomeItem>>().apply {
value = arrayListOf()
}
val homeItem get() = _homeItem
val homeArrayList = arrayListOf<HomeItem>()
init {
val sellDao = MainDatabase.getDatabase(application).sellDao()
val postDao = MainDatabase.getDatabase(application).postDao()
sellRepository = SellRepository(sellDao)
postRepository = PostRepository(postDao)
sellList = sellRepository.getAllSellList()
postList = postRepository.getPostList()
}
fun addSellItemsToArray() {
for (sell in sellList.value!!) {
val homeItem = HomeItem()
homeItem.userName = sell.userName
homeItem.userImage = sell.userImage
homeItem.imagesArray = sell.imagesArray
homeItem.desc = sell.desc
homeItem.itemForSell = sell.itemForSell
homeItem.price = sell.price
homeItem.country = sell.country
homeItem.city = sell.city
homeItem.address = sell.address
homeArrayList.add(homeItem)
}
}
fun addPostItemToArray() {
for (post in postList.value!!) {
val homeItem = HomeItem()
homeItem.userName = post.postedUserName
homeItem.userImage = post.userUrl
homeItem.imagesArray = arrayListOf(post.postUrl)
homeItem.desc = post.commit
homeItem.likesCount = post.likesCount
homeArrayList.add(homeItem)
}
}
It seems to me I'm doing wrong to make the observer in the observer in home fragment.
Yes, there are better ways to handle this scenario.
One of this is the one below:
class YourViewModel() : ViewModel() {
private val tempHomeList = mutableListOf<HomeItem>()
private val _homeList = MutableLiveData<HomeItem>()
val homeList: LiveData<HomeItem> = _homeList
private lateinit var sellRepository: SellRepository
private lateinit var postRepository: PostRepository
init {
val sellDao = MainDatabase.getDatabase(application).sellDao()
val postDao = MainDatabase.getDatabase(application).postDao()
sellRepository = SellRepository(sellDao)
postRepository = PostRepository(postDao)
}
fun initList() {
addSellItemsToArray()
addPostItemToArray()
_homeList.value = tempHomeList
}
private fun addSellItemsToArray() {
val sellList = sellRepository.getAllSellList()
for (sell in sellList.value!!) {
val homeItem = HomeItem()
homeItem.userName = sell.userName
homeItem.userImage = sell.userImage
homeItem.imagesArray = sell.imagesArray
homeItem.desc = sell.desc
homeItem.itemForSell = sell.itemForSell
homeItem.price = sell.price
homeItem.country = sell.country
homeItem.city = sell.city
homeItem.address = sell.address
tempHomeList.add(homeItem)
}
}
private fun addPostItemToArray() {
val postList = postRepository.getPostList()
for (post in postList.value!!) {
val homeItem = HomeItem()
homeItem.userName = post.postedUserName
homeItem.userImage = post.userUrl
homeItem.imagesArray = arrayListOf(post.postUrl)
homeItem.desc = post.commit
homeItem.likesCount = post.likesCount
tempHomeList.add(homeItem)
}
}
}
Then in your Fragment do something like:
class YourFragment : Fragment {
private val recyclerAdapter = YourRecyclerAdapter()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
//init your views, view model and your recycler view adapter
//...
recyclerView.adapter = recyclerAdapter
}
//...
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
viewModel.homeList.observe(viewLifecycleOwner, { list ->
recyclerViewAdapter.updateList(list)
})
//start observing other liveData or do anything else here
//....
viewModel.initList()
}
}
And a sample of your Adapter...
class YourRecyclerAdapter : RecyclerView.Adapter<HomeItemAdapter.ViewHolder> {
private val homeList = mutableListOf<HomeItem>()
fun updateList(list: List<HomeItem>) {
homeList.clear()
homeList.addAll(list)
notifyDataSetChanged()
}
//.... the rest of your adapter and viewholder
}
I'm trying to implement Recyclerview in my kotlin code....
And I'm using Retrofit getting data from webservice and plot it into recycler view
MainActivity.class
class MainActivity : AppCompatActivity() {
internal lateinit var jsonApi:MyAPI
private val compositeDisposable = CompositeDisposable()
lateinit var recyclerView: RecyclerView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
recyclerView = findViewById(R.id.recycler_drivers)
// init API
val retrofitt = RetrofitClient.instance
if (retrofitt != null) {
jsonApi = retrofitt.create(MyAPI::class.java)
}
//View
recyclerView.setHasFixedSize(true)
recyclerView.layoutManager = LinearLayoutManager(this)
fetchData()
}
private fun fetchData() {
compositeDisposable.add(jsonApi.drivers
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe{drivers->displayData(drivers)}
)
}
private fun displayData(drivers: List<Driver>?) {
val adapter = DriverAdapter(this,drivers!!)
recycler_drivers.adapter = adapter
}
}
Adapter.class
class DriverAdapter(internal var contex:Context, internal var driverList:List<Driver>): RecyclerView.Adapter<DriverViewHolder>()
{
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DriverViewHolder {
val itemView = LayoutInflater.from(parent.context)
.inflate(R.layout.driver_layout, parent, false)
return DriverViewHolder(itemView)
}
override fun getItemCount(): Int {
return driverList.size
}
override fun onBindViewHolder(holder: DriverViewHolder, position: Int) {
holder.txt_driver_number.text = driverList[position].driver_number
holder.txt_first_name.text = driverList[position].first_name
holder.txt_ph_number.text = driverList[position].ph_number.toString()
}
}
ViewHolder.class
class DriverViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val txt_driver_number = itemView.txt_driver_number
val txt_first_name = itemView.txt_first_name
val txt_ph_number = itemView.txt_ph_number
}
This is the API interface
interface MyAPI {
#get:GET("data")
val drivers:Observable<List<Driver>>
}
RetrofitClient Object
object RetrofitClient {
private var ourInstance : Retrofit? = null
var instance: Retrofit? = null
get(){
if(ourInstance == null){
ourInstance = Retrofit.Builder()
.baseUrl("http://localhost/BSProject/public/")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build()
}
return ourInstance!!
}
}
and this is the Model class which is basically the data coming form my localhost server
class Driver {
var driver_number: String = ""
var first_name: String = ""
var ph_number: Int = 0
}
As you can see I have attached an adapter for Recycleview. so why do I keep getting this error?
I have read other questions related to same problem, but none helps.
Either build the recyclerView inside your displayData()
private fun displayData(drivers: MutableList<Driver>?) {
recyclerView.setHasFixedSize(true)
recyclerView.layoutManager = LinearLayoutManager(this)
val adapter = DriverAdapter(this,drivers!!)
recycler_drivers.adapter = adapter
}
Or do what Gabriele Suggested where you attach your adapter to the recyclerviewin onCreate() and add your response data to your adapter after having made the call. This is the ideal approach
class MainActivity: {
lateinit var driverAdapter: DriverAdapter
protected void onCreate() {
...
recyclerView = findViewById(R.id.recycler_drivers)
recyclerView.setHasFixedSize(true)
recyclerView.layoutManager = LinearLayoutManager(this)
val adapter = DriverAdapter(this)
recycler_drivers.adapter = adapter
}
private fun displayData(drivers: List<Driver>?) {
driverAdapter.setDrivers(drivers)
}
And you'd expose a method in your adapter to set the data setDrivers()
class DriverAdapter(internal var contex:Context):
RecyclerView.Adapter<DriverViewHolder>()
{
val drivers = mutableListOf()
...
fun setDrivers(drivers: MutableList<Driver>) {
this.drivers = drivers
notifyDataSetChanged()
}
}
This will get rid of your No adapter attached; skipping layout :RecyclerView error
I think you are seeing this issue because of the asynchronous nature of querying the web service through retrofit. You don't actually assign the RecyclerView.Adapter until after onCreate exits.
Try changing the visiblility of the RecyclerView to Gone until the adapter is applied in displayData, then set it to Visible