I'm try to initialize recyclerView and ViewModel in onCreate
in fragment and get error lateinit property recyclerViewAdapter has not been initialized
class ListFragment : Fragment() {
private val recyclerView: RecyclerView? = activity?.findViewById(R.id.recyclerView)
private lateinit var recyclerViewAdapter: RecyclerViewAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
initRecyclerView()
initViewModel()
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_list, container, false)
}
private fun initRecyclerView() {
recyclerView?.apply {
layoutManager = LinearLayoutManager(context)
val decoration = DividerItemDecoration(context, DividerItemDecoration.VERTICAL)
addItemDecoration(decoration)
recyclerViewAdapter = RecyclerViewAdapter()
adapter = recyclerViewAdapter
}
}
private fun initViewModel() {
val viewModel = ViewModelProvider(this)[MainActivityViewModel::class.java]
lifecycleScope.launchWhenCreated {
viewModel.getListData().collectLatest {
recyclerViewAdapter.submitData(it)
}
}
}
}
CodePudding user response:
Here's where you went wrong:
private val recyclerView: RecyclerView? = activity?.findViewById(R.id.recyclerView)
The above property will be null because activity
is null at instantiation time of the Fragment, and you are using activity?.
so you are initializing it as null
.
Then in initRecyclerView()
, you are using recyclerView?.apply
, so nothing in the lambda will be executed because recyclerView
is null. Therefore, the property recyclerViewAdapter
is never set, so when it is first accessed (in initViewModel()
), it will throw UnitializedPropertAccessException and crash.
You must not initialize a Fragment's view properties at the declaration site, because a Fragment is initialized before it is has any view or is attached to any Activity. Also, the same Fragment instance might be reused but have a new view and Activity instance, so you should make sure you are re-assigning these properties every time there is a new view.
Also, don't initialize views in onCreate()
, because it happens before your onCreateView()
, so there will be no view to find. You should rarely ever need to use onCreate()
in a Fragment. Use onViewCreated()
instead.
And if the recycler view is in your Fragment layout, you should search the Fragment's view for the RecyclerView instead of searching up in the Activity. The Activity still does not contain the Fragment's views in onViewCreated()
so it will fail to find the view.
Here's how to fix your code:
class ListFragment : Fragment() {
private lateinit var recyclerView: RecyclerView
private lateinit var recyclerViewAdapter: RecyclerViewAdapter
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_list, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onCreate(view, savedInstanceState)
recyclerView = view.findViewById(R.id.recyclerView)
initRecyclerView()
initViewModel()
}
private fun initRecyclerView() {
recyclerView.apply {
layoutManager = LinearLayoutManager(context)
val decoration = DividerItemDecoration(context, DividerItemDecoration.VERTICAL)
addItemDecoration(decoration)
recyclerViewAdapter = RecyclerViewAdapter()
adapter = recyclerViewAdapter
}
}
private fun initViewModel() {
//...
}
}
The above technically leaks your views when the fragment is detached. You could avoid this by using custom getters so you aren't caching references to the views. Also, you can eliminate onCreateView
by passing your layout ID directly to the Fragment super-constructor. So here's how I would write this class:
class ListFragment : Fragment(R.layout.fragment_list) {
private val recyclerView: RecyclerView
get() = requireView().findViewById(R.id.recyclerView)
private val recyclerViewAdapter: RecyclerViewAdapter
get() = recyclerView.adapter as RecyclerViewAdapter
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onCreate(view, savedInstanceState)
initRecyclerView()
initViewModel()
}
private fun initRecyclerView() {
recyclerView.apply {
layoutManager = LinearLayoutManager(context)
val decoration = DividerItemDecoration(context, DividerItemDecoration.VERTICAL)
addItemDecoration(decoration)
adapter = RecyclerViewAdapter()
}
}
private fun initViewModel() {
//...
}
}