Search code examples
androidandroid-studioandroid-fragmentsandroid-adapterandroid-viewmodel

Setting up the Adapter in my Fragment so that it works


When I tried running the app, I got this errorenter image description here

I've tried different ways to implement the adapter into the fragment, but it doesn't seem to work

The Adapter

class AsteroidViewAdapter(private val onClickListener: OnClickListener)  :
    ListAdapter<Asteroid, AsteroidViewAdapter.AsteroidViewHolder>(DiffCallback) {




    companion object DiffCallback : DiffUtil.ItemCallback<Asteroid>() {
        override fun areItemsTheSame(oldItem: Asteroid, newItem: Asteroid): Boolean {
            return oldItem.id == newItem.id
        }

        override fun areContentsTheSame(oldItem: Asteroid, newItem: Asteroid): Boolean {
            return oldItem == newItem
        }

    }

    override fun onCreateViewHolder(
        parent: ViewGroup,
        viewType: Int
    ): AsteroidViewHolder {
        return AsteroidViewHolder(
            AsteroidListContainerBinding.inflate(
                LayoutInflater.from(parent.context),
                parent,
                false
            )
        )
    }

    override fun onBindViewHolder(holder: AsteroidViewHolder, position: Int) {
        holder.bind(getItem(position), onClickListener)
    }

    class AsteroidViewHolder(private val binding: AsteroidListContainerBinding) :
        RecyclerView.ViewHolder(binding.root) {
        fun bind(item: Asteroid, listener: OnClickListener) {
            binding.value = item
            binding.listener = listener
            binding.executePendingBindings()
        }


    }


    class OnClickListener(val clickListener: (asteroid: Asteroid) -> Unit) {
        fun onClick(asteroid: Asteroid) = clickListener(asteroid)
    }

}

The ViewModel

private const val TAG = "MainViewModel"

class MainViewModel(application: Application) : ViewModel() {

    private val database = AsteroidDatabase.getDatabase(application)

    private val repository = AsteroidRepository(database.asteroidDao)

    private val _pictureOfDay = MutableLiveData<PictureOfDay>()
    val pictureOfDay: LiveData<PictureOfDay>
        get() = _pictureOfDay

   val info: LiveData<List<Asteroid>> = Transformations.map(repository.feeds) {
       it
   }


    private val _showProgress = MutableLiveData(true)
    val showProgress: LiveData<Boolean>
        get() = _showProgress

    private val _navigation: MutableLiveData<Asteroid> = MutableLiveData()
    val navigation: LiveData<Asteroid>
        get() = _navigation


    init {
        fetchThePictureOfTheDay()
        loadFeeds()
    }

    private fun loadFeeds() {
        _showProgress.value =  true
        viewModelScope.launch {
            try {
                repository.fetchFeeds()
            }catch (e: Exception) {
                Log.e(TAG, e.message, e.cause)
            }
        }
    }

    private fun fetchThePictureOfTheDay() {
        viewModelScope.launch {
            try {
                val picture = repository.getPictureOfTheDay()
                _pictureOfDay.postValue(picture)
            }catch (e: Exception) {
                Log.e(TAG, e.message, e.cause)
            }
        }
    }


    fun navigateToDetails(asteroid: Asteroid) {
        _navigation.value = asteroid
    }

    fun navigationDone() {
        _navigation.value = null
    }

    fun progress(empty: Boolean) {
        _showProgress.value = empty
    }

    fun filter(filter: Filter) {
        viewModelScope.launch(Dispatchers.IO) {
            repository.filterFeeds(filter)
        }
    }

}

The Fragment

class MainFragment : Fragment() {

//    private lateinit var  manager: RecyclerView.LayoutManager

  private lateinit var viewModel: MainViewModel
  private lateinit var adapter: AsteroidViewAdapter

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {

        val mainViewModelFactory = MainViewModelFactory(requireActivity().application)
        viewModel = ViewModelProvider(this, mainViewModelFactory).get(MainViewModel::class.java)


        val binding = FragmentMainBinding.inflate(inflater)
        binding.lifecycleOwner = this

        binding.viewModel = viewModel

//        val mutableList: MutableList<Asteroid> = ArrayList()
//        mutableList.add(Asteroid(1, "fgnuugrhrg", "bihagtyjerwailgubivb", 4.0, 8.0,3.0, 9.0, false))
//        mutableList.add(Asteroid(2, "fguk.nuugrhrg", "bidjswjyhagrwailgubivb", 3.0, 90.0,355.0, 9.0, true))
//        mutableList.add(Asteroid(3, "fgnssuugrhrg", "bshjtihagrwailgubivb", 4.0, 33.0,33.0, 9.0, false))
//        mutableList.add(Asteroid(4, "fgnuw4suugrhrg", "bjsryjihagrwailgubivb", 6.0, 8.0,11.0, 9.0, true))
//        mutableList.add(Asteroid(5, "fgnuugrudkdkhrg", "bihjjkkuagrwailgubivb", 4.0, 5.0,77.0, 9.0, false))

//        manager = LinearLayoutManager(this.context)


         binding.asteroidRecycler.adapter = AsteroidViewAdapter(AsteroidViewAdapter.OnClickListener {
             viewModel.navigateToDetails(it)
         })

        viewModel.info.observe(viewLifecycleOwner, Observer {
            viewModel.progress(it.isNullOrEmpty())
            binding.asteroidRecycler.smoothScrollToPosition(0)
            adapter.submitList(it)
        })

       viewModel.navigation.observe(viewLifecycleOwner, Observer { asteroid ->
           asteroid?.let {
               findNavController().navigate(MainFragmentDirections.actionShowDetail(it))
               viewModel.navigationDone()
           }
       })


//        binding.asteroidRecycler.adapter = adapter


        setHasOptionsMenu(true)

        return binding.root
    }


    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
        inflater.inflate(R.menu.main_overflow_menu, menu)
        super.onCreateOptionsMenu(menu, inflater)
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        return when (item.itemId) {
            R.id.menu_show_week -> {
                viewModel.filter(Filter.WEEK)
                true
            }
            R.id.menu_show_today -> {
                viewModel.filter(Filter.TODAY)
                true
            }
            R.id.menu_show_saved -> {
                viewModel.filter(Filter.SAVED)
                true
            }
            else -> false
        }
    }
}

Solution

  • As per the error message, you've never actually set your adapter to any value - you just directly assign it to binding.asteroidRecycler.adapter.

    Since an adapter keeps a hard reference to the RecyclerView itself, you shouldn't hold a reference to it at the fragment level anyways, you should instead create a local variable for it before assigning it to your RecyclerView:

    val adapter = binding.asteroidRecycler.adapter = AsteroidViewAdapter(AsteroidViewAdapter.OnClickListener {
                 viewModel.navigateToDetails(it)
             })
    binding.asteroidRecycler.adapter = adapter