I am trying to figure out how to pass an integer from a fragment to a viewmodel while using hilt. I have ready that viewmodel factories can be used for this, I am not sure how this would be done using DI.
In the code below, I am trying to figure out how I can pass albumId to the viewModel. The albumId will be used when fetching data from an API endpoint.
Fragment
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val view = inflater.inflate(R.layout.fragment_album_details, container, false)
val albumId = arguments?.getInt("album_id")
viewModel.songs.observe(viewLifecycleOwner) {
view.song_recyclerview.apply {
layoutManager = LinearLayoutManager(this.context)
adapter = SongAdapter(viewModel.songs)
}
}
return view
}
ViewModel
class SongViewModel #ViewModelInject constructor(
songRepo: SongRepository,
#Assisted savedStateHandle: SavedStateHandle
) : ViewModel(), LifecycleObserver {
val songs: LiveData<List<Song>> = songRepo.getSongs(1)
}
Repository
class SongRepository constructor(
private val musicService: MusicService
)
{
fun getSongs(album_id: Int): LiveData<List<Song>> {
val data = MutableLiveData<List<Song>>()
musicService.getAlbumTracks(album_id).enqueue(object : Callback<List<Song>> {
override fun onResponse(call: Call<List<Song>>, response: Response<List<Song>>) {
data.value = response.body()
}
override fun onFailure(call: Call<List<Song>>, t: Throwable) {
}
})
return data
}
}
I was finally able to figure out a solution to the problem. I added a field to the viewmodel, and a method to set a value for that field. Basically, I call viewModel.start(int) then call viewModel.songs.
ViewModel
class SongViewModel #ViewModelInject constructor(
songRepo: SongRepository,
#Assisted savedStateHandle: SavedStateHandle
) : ViewModel(), LifecycleObserver {
private val _id = MutableLiveData<Int>() // int field
private val _songs = _id.switchMap { id ->
songRepo.getSongs(id)
}
val songs: LiveData<List<Song>> = _songs
fun start(id: Int) { // method to update field
_id.value = id
}
}
Fragment
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// get data from bundle and pass to start()
arguments?.getInt(("album_id"))?.let { viewModel.start(it)}
viewModel.songs.observe(viewLifecycleOwner) {
view.song_recyclerview.apply {
layoutManager = LinearLayoutManager(this.context)
adapter = SongAdapter(viewModel.songs)
}
}
}
Related
Well I am a beginner with android and kotlin so I have been trying to send a variable semesterSelected from the fragment ViewCourses to my viewmodel UserViewModel is the codes are down below.
`class ViewCourses(path: String) : ReplaceFragment() {
private var semesterSelected= path
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
container?.removeAllViews()
return inflater.inflate(R.layout.fragment_view_courses, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
userRecyclerView = view.findViewById(R.id.recyclerView)
userRecyclerView.layoutManager = LinearLayoutManager(context)
userRecyclerView.setHasFixedSize(true)
adapter = MyAdapter()
userRecyclerView.adapter = adapter
makeToast(semesterSelected)
// The variable I am trying to send to UserViewModel is -->> semesterSelected
var viewModel: UserViewModel = ViewModelProvider(this)[UserViewModel::class.java]
viewModel.allUsers.observe(viewLifecycleOwner) {
adapter.updateUserList(it)
}
}
}
class UserViewModel : ViewModel() {
private val repository: UserRepository = UserRepository("CSE/year3semester1").getInstance()
private val _allUsers = MutableLiveData<List<CourseData>>()
val allUsers: LiveData<List<CourseData>> = _allUsers
init {
repository.loadUsers(_allUsers)
}
}
The reason I am doing this is I am wanting a to send a variable to my repository UserRepository all the way from ViewCourses and thought sending this via UserViewModel might be a way .
class UserRepository(semesterSelected: String) {
// The variable I am expecting to get from UserViewModel
private var semesterSelected = semesterSelected
private val databaseReference: DatabaseReference =
FirebaseDatabase.getInstance().getReference("course-list/$semesterSelected")
#Volatile
private var INSTANCE: UserRepository? = null
fun getInstance(): UserRepository {
return INSTANCE ?: synchronized(this) {
val instance = UserRepository(semesterSelected)
INSTANCE = instance
instance
}
}
fun loadUsers(userList: MutableLiveData<List<CourseData>>) {
databaseReference.addValueEventListener(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
try {
val courseList: List<CourseData> = snapshot.children.map { dataSnapshot ->
dataSnapshot.getValue(CourseData::class.java)!!
}
userList.postValue(courseList)
} catch (e: Exception) {
}
}
override fun onCancelled(error: DatabaseError) {
}
})
}
}
I tried something like below
class ViewCourses(path: String) : ReplaceFragment() {
private var semesterSelected= path
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
container?.removeAllViews()
return inflater.inflate(R.layout.fragment_view_courses, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
userRecyclerView = view.findViewById(R.id.recyclerView)
userRecyclerView.layoutManager = LinearLayoutManager(context)
userRecyclerView.setHasFixedSize(true)
adapter = MyAdapter()
userRecyclerView.adapter = adapter
makeToast(semesterSelected)
**// Sending the variable as parameter**
var viewModel: UserViewModel = ViewModelProvider(this)[UserViewModel(semesterSelected)::class.java]
viewModel.allUsers.observe(viewLifecycleOwner) {
adapter.updateUserList(it)
}
}
}
class UserViewModel(semesterSelected: String) : ViewModel() {
private val repository: UserRepository = UserRepository("CSE/year3semester1").getInstance()
private val _allUsers = MutableLiveData<List<CourseData>>()
val allUsers: LiveData<List<CourseData>> = _allUsers
init {
repository.loadUsers(_allUsers)
}
}
but doing this my app crashes . how can this be done ?
Thanks in Advance.
var viewModel: UserViewModel = ViewModelProvider(this)[UserViewModel(semesterSelected)::class.java]
UserViewModel(semesterSelected)::class.java NOR UserViewModel::class.java is a constructor for the view model.
If you would want to have ViewModel with that NEEDS initial parameters, you will have to create your own factory for that - which is a tad more complicated and for your case, it might be overkill for what you are trying to do but in the longterm it will pay off(Getting started with VM factories).
With that said, your needs can be easily solved by one function to initialize the view model.
class UserViewModel() : ViewModel() {
private lateinit var repository: UserRepository
private val _allUsers = MutableLiveData<List<CourseData>>()
val allUsers: LiveData<List<CourseData>> = _allUsers
fun initialize(semesterSelected: String) {
repository = UserRepository("CSE/year3semester1").getInstance()
repository.loadUsers(_allUsers)
}
}
A ViewModel must be created using a ViewModelProvider.Factory. But there is a default Factory that is automatically used if you don't specify one. The default factory can create ViewModels who have constructor signatures that are one of the following:
empty, for example MyViewModel: ViewModel.
saved state handle, for example MyViewModel(private val savedStateHandle: SavedStateHandle): ViewModel
application, for example MyViewModel(application: Application): AndroidViewModel(application)
both, for example MyViewModel(application: Application, private val savedStateHandle: SavedStateHandle): AndroidViewModel(application)
If your constructor doesn't match one of these four above, you must create a ViewModelProvider.Factory that can instantiate your ViewModel class and use that when specifying your ViewModelProvider. In Kotlin, you can use by viewModels() for easier syntax. All the instructions for how to create your ViewModelFactory are here.
I am studying the MVVM pattern.
I have a question regarding LiveData while using ViewModel class.
Even if I do not change the value of LiveData with setValue or postValue, it continues to observe and execute the fragment.
When addRoutine() is called, vm.observe also continues to run.
As you can see there is no setValue or postValue in addRoutine(), so LiveData has no value change at all.
But why does vm.observe keep running?
This is my code.
ViewModel.kt
class WriteRoutineViewModel : ViewModel() {
private val _items: MutableLiveData<List<RoutineModel>> = MutableLiveData(listOf())
private val rmList = arrayListOf<RoutineModel>()
val items: LiveData<List<RoutineModel>> = _items
fun addRoutine(workout: String) {
val rmItem = RoutineModel(UUID.randomUUID().toString(), workout, "TEST")
rmItem.getSubItemList().add(RoutineDetailModel("2","3","3123"))
rmList.add(rmItem)
// _items.postValue(rmList)
}
fun getListItems() : List<RoutineItem> {
val listItems = arrayListOf<RoutineItem>()
for(testRM in rmList) {
listItems.add(RoutineItem.RoutineModel(testRM.id,testRM.workout,testRM.unit))
val childListItems = testRM.getSubItemList().map { detail ->
RoutineItem.DetailModel("2","23","55")
}
listItems.addAll(childListItems)
}
return listItems
}
}
Fragment
class WriteRoutineFragment : Fragment() {
private var _binding : FragmentWriteRoutineBinding? = null
private val binding get() = _binding!!
private val vm : WriteRoutineViewModel by viewModels { WriteRoutineViewModelFactory() }
private lateinit var epoxyController : RoutineItemController
override fun onCreateView(inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?): View? {
_binding = FragmentWriteRoutineBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
getTabPageResult()
// RecyclerView(Epoxy) Update
vm.items.observe(viewLifecycleOwner) { updatedItems ->
epoxyController.setData(vm.getListItems())
}
}
private fun getTabPageResult() {
val navController = findNavController()
navController.currentBackStackEntry?.also { stack ->
stack.savedStateHandle.getLiveData<String>("workout")?.observe(
viewLifecycleOwner, Observer { result ->
vm.addRoutine(result)
stack.savedStateHandle?.remove<String>("workout")
}
)
}
}
}
I have a parent fragment which fetches a list from API using ViewModel and Retrofit, the ViewModel is injected with Hilt.
After the list gets fetched the parent fragment will pass to its child fragment that is inside of parent fragment.
but the problem is that ViewModel is instantiated one more time in the child fragment.
Parent Fragment
#AndroidEntryPoint
class ParentFragment : Fragment() {
override val mViewModel: URLViewModel by viewModels()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
mViewBinding = getViewBinding(inflater, container)
mViewModel.liveData.observe(this, { data ->
{
childFragmentManager.beginTransaction().apply {
replace(
mViewBinding.fragmentContainer.id,
ChildFragment(data)
)
}
commit()
} })
mViewModel.getURL("TEST", "2021-06-18", "2021-07-18", 1 , 0 , -1, false)
return mViewBinding.root
}
}
ChildFragment
#AndroidEntryPoint
class ChildFragment(val data: List<Item>) : Fragment() {
override val mViewModel: URLViewModel by viewModels()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
mViewBinding = getViewBinding(inflater, container)
// mViewModel is instantiated again and some all strings properties of it is null.
return mViewBinding.root
}
}
URLViewModel
#HiltViewModel
class URLViewModel #Inject constructor(private val urlApi: URLApi): ViewModel() {
private val _urlLiveData = MutableLiveData<State<Any?>>()
val urlLiveData: LiveData<State<Any?>> = _urlLiveData
var urlName: String? = null
var beginDate: String? = null
var endDate: String? = null
var adultCount = 0
var childrenCount = 0
var airportId = 0
var isRoundTrip = false
init {
Log.e("URLViewModel", "iniialed again" )
}
#ExperimentalStdlibApi
fun getUrl(urlName: String, beginDate: String, endDate: String, adultCount: Int, childCount: Int, airportId: Int, isRoundTrip: Boolean){
Log.e("XXXXXX", "getUrl: called with url of " + urlName )
this.urlName = urlName
this.beginDate = beginDate
this.endDate = endDate
this.adultCount = adultCount
this.childrenCount = childCount
this.airportId = airportId
this.isRoundTrip = isRoundTrip
val mutableLiveData = MutableLiveData<State<Any?>>()
mutableLiveData.value = State.loading()
viewModelScope.launch {
val res = urlApi.getURL(urlName,beginDate,endDate,adultCount,childCount,airportId,isRoundTrip)
Log.e("URLVIewModel", "getUrl: response received" )
_urlLiveData.value = res
}
}
}
when I wanna access some properties like beginDate, they are null, because the ViewModel is instantiated again,
viewModels() delegation create view model against the same instance i.e Fragment's instance in your case. What you need to do is to create a shared View model .
There is helper delegate available for it with ktx libraries.
add the ktx dependency which you already have i guess from here.
implementation "androidx.fragment:fragment-ktx:1.3.4"
And create view model with
private val viewModel by activityViewModels<UrlViewModel>()
You do not have to use activity shared view model. Simply request view model from parent fragment in ChildFragment.
private val viewModel by viewModels<UrlViewModel>(ownerProducer = { requireParentFragment() })
You are trying to use share viewmodel. Try to following code for reference.
#AndroidEntryPoint
class ParentFragment : Fragment() {
private lateinit var viewModel: URLViewModel
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(requireActivity()).get(URLViewModel::class.java)
}
}
#AndroidEntryPoint
class ChildFragment : Fragment() {
private lateinit var viewModel: URLViewModel
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(requireActivity()).get(URLViewModel::class.java)
}
}
I have a ShopFilterFragmentProductFilter which is inside a ShopFilterFragmentHolder which itself holds a ViewPager2. This ShopFilterFragmentHolder is a DialogFragment which is opened inside my ShopFragment. So ShopFragment -> ShopFilterFragmentHolder (Dialog, ViewPager2) -> ShopFilterFragmentProductFilter. ALL of these Fragments should share the same navgraphscoped viewmodel.
The problem I have is, that when I attach an observer inside my ShopFilterFragmentProductFilter to get my recyclerview list from cloud-firestore, this observer never gets called and therefore I get the error message "No Adapter attached, skipping layout". I know that this is not a problem with how I instantiate and assign the adapter to my recyclerview, because when I set a static list (e.g creating a list inside my ShopFilterFragmentProductFilter) everything works.
Why do I don't get the livedata value? To my mind, there is a problem with the viewmodel creation.
Here is my current approach:
ShopFilterFragmentProductFilter
#AndroidEntryPoint
class ShopFilterFragmentProductFilter : Fragment() {
private var _binding: FragmentShopFilterItemBinding? = null
private val binding: FragmentShopFilterItemBinding get() = _binding!!
private val shopViewModel: ShopViewModel by navGraphViewModels(R.id.nav_shop) { defaultViewModelProviderFactory }
#Inject lateinit var shopFilterItemAdapter: ShopFilterItemAdapter
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentShopFilterItemBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
bindObjects()
submitAdapterList()
}
override fun onDestroyView() {
super.onDestroyView()
binding.rvShopFilter.adapter = null
_binding = null
}
private fun bindObjects() {
with(binding) {
adapter = shopFilterItemAdapter
}
}
private fun submitAdapterList() {
shopViewModel.shopProductFilterList.observe(viewLifecycleOwner) {
shopFilterItemAdapter.submitList(it)
shopFilterItemAdapter.notifyDataSetChanged()
toast("SUBMITTED LIST") // this does never get called
}
/* // this works
shopFilterItemAdapter.submitList(
listOf(
ShopFilterItem(0, "ITEM 1"),
ShopFilterItem(0, "ITEM 2"),
ShopFilterItem(0, "ITEM 3"),
ShopFilterItem(0, "ITEM 4"),
ShopFilterItem(0, "ITEM 5"),
)
)
*/
}
}
ViewModel
class ShopViewModel #ViewModelInject constructor(
private val shopRepository: ShopRepository,
private val shopFilterRepository: ShopFilterRepository
) : ViewModel() {
private val query = MutableLiveData(QueryHolder("", ""))
val shopPagingData = query.switchMap { query -> shopRepository.search(query).cachedIn(viewModelScope) }
val shopProductFilterList: LiveData<List<ShopFilterItem>> = liveData { shopFilterRepository.getProductFilterList() }
val shopListFilterList: LiveData<List<ShopFilterItem>> = liveData { shopFilterRepository.getListFilterList() }
fun search(newQuery: QueryHolder) {
this.query.value = newQuery
}
}
ShopFilterRepositoryImpl
class ShopFilterRepositoryImpl #Inject constructor(private val db: FirebaseFirestore) : ShopFilterRepository {
override suspend fun getProductFilterList(): List<ShopFilterItem> = db.collection(FIREBASE_SERVICE_INFO_BASE_PATH)
.document(FIREBASE_SHOP_FILTER_BASE_PATH)
.get()
.await()
.toObject<ShopFilterItemHolder>()!!
.productFilter
override suspend fun getListFilterList(): List<ShopFilterItem> = db.collection(FIREBASE_SERVICE_INFO_BASE_PATH)
.document(FIREBASE_SHOP_FILTER_BASE_PATH)
.get()
.await()
.toObject<ShopFilterItemHolder>()!!
.listFilter
}
Nav_graph
Probably, you should define it as MutableLiveData:
private val shopProductFilterList: MutableLiveData<List<ShopFilterItem>> = MutableLiveData()
And in a method in your viewModel that gets the data through repository, you should post the LiveData value:
fun getProductFilterList() = viewModelScope.launch {
val dataFetched = repository. getProductFilterList()
shopProductFilterList.postValue(dataFetched)
}
I'm using Dagger 2 in clean architecture project, I have 2 fragments. These 2 fragments should be scoped together to share the same instances, but unfortunately, I got empty object in the second fragment.
Application Component
#ApplicationScope
#Component(modules = [ContextModule::class, RetrofitModule::class])
interface ApplicationComponent {
fun exposeRetrofit(): Retrofit
fun exposeContext(): Context
}
Data layer - Repository
class MoviesParsableImpl #Inject constructor(var moviesLocalResult: MoviesLocalResult): MoviesParsable {
private val TAG = javaClass.simpleName
private val fileUtils = FileUtils()
override fun parseMovies() {
Log.d(TAG,"current thread is ".plus(Thread.currentThread().name))
val gson = Gson()
val fileName = "movies.json"
val jsonAsString = MyApplication.appContext.assets.open(fileName).bufferedReader().use{
it.readText()
}
val listType: Type = object : TypeToken<MoviesLocalResult>() {}.type
moviesLocalResult = gson.fromJson(jsonAsString,listType)
Log.d(TAG,"result size ".plus(moviesLocalResult.movies?.size))
}
override fun getParsedMovies(): Results<MoviesLocalResult> {
return Results.Success(moviesLocalResult)
}
}
Repo Module
#Module
interface RepoModule {
#DataComponentScope
#Binds
fun bindsMoviesParsable(moviesParsableImpl: MoviesParsableImpl): MoviesParsable
}
MoviesLocalResultsModule(the result need its instance across different fragments)
#Module
class MoviesLocalResultModule {
#DataComponentScope
#Provides
fun provideMovieLocalResults(): MoviesLocalResult{
return MoviesLocalResult()
}
}
Use case
class AllMoviesUseCase #Inject constructor(private val moviesParsable: MoviesParsable){
fun parseMovies(){
moviesParsable.parseMovies()
}
fun getMovies(): Results<MoviesLocalResult> {
return moviesParsable.getParsedMovies()
}
}
Presentation Component
#PresentationScope
#Component(modules = [ViewModelFactoryModule::class],dependencies = [DataComponent::class])
interface PresentationComponent {
fun exposeViewModel(): ViewModelFactory
}
First ViewModel, where I got the result to be shared with the other fragment when needed
class AllMoviesViewModel #Inject constructor(private val useCase: AllMoviesUseCase):ViewModel() {
private val moviesMutableLiveData = MutableLiveData<Results<MoviesLocalResult>>()
init {
moviesMutableLiveData.postValue(Results.Loading())
}
fun parseJson(){
viewModelScope.launch(Dispatchers.Default){
useCase.parseMovies()
moviesMutableLiveData.postValue(useCase.getMovies())
}
}
fun readMovies(): LiveData<Results<MoviesLocalResult>> {
return moviesMutableLiveData
}
}
Second ViewModel where no need to request data again as it's expected to be scoped
class MovieDetailsViewModel #Inject constructor(private val useCase: AllMoviesUseCase): ViewModel() {
var readMovies = liveData(Dispatchers.IO){
emit(Results.Loading())
val result = useCase.getMovies()
emit(result)
}
}
First Fragment, where data should be requested:
class AllMoviesFragment : Fragment() {
private val TAG = javaClass.simpleName
private lateinit var viewModel: AllMoviesViewModel
private lateinit var adapter: AllMoviesAdapter
private lateinit var layoutManager: LinearLayoutManager
private var ascendingOrder = true
#Inject
lateinit var viewModelFactory: ViewModelFactory
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
DaggerAllMoviesComponent.builder()
.presentationComponent(
DaggerPresentationComponent.builder()
.dataComponent(
DaggerDataComponent.builder()
.applicationComponent(MyApplication.applicationComponent).build()
)
.build()
).build()inject(this)
viewModel = ViewModelProvider(this, viewModelFactory).get(AllMoviesViewModel::class.java)
startMoviesParsing()
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_all_movies, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
setupRecyclerView()
viewModel.readMovies().observe(viewLifecycleOwner, Observer {
if (it != null) {
when (it) {
is Loading -> {
showResults(false)
}
is Success -> {
showResults(true)
Log.d(TAG, "Data observed ".plus(it.data))
addMoviesList(it.data)
}
is Error -> {
moviesList.snack(getString(R.string.error_fetch_movies))
}
}
}
})
}
Second Fragment, where I expect to get the same instance request in First Fragment as they are scoped.
class MovieDetailsFragment: Fragment() {
val TAG = javaClass.simpleName
#Inject
lateinit var viewModelFactory: ViewModelFactory
lateinit var viewModel: MovieDetailsViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val depend = DaggerAllMoviesComponent.builder()
.presentationComponent(
DaggerPresentationComponent.builder()
.dataComponent(
DaggerDataComponent.builder()
.applicationComponent(MyApplication.applicationComponent).build())
.build()
).build()
depend.inject(this)
viewModel = ViewModelProvider(this, viewModelFactory).get(MovieDetailsViewModel::class.java)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
viewModel.readMovies.observe(this, Observer {
if (it!=null){
Log.d(TAG,"Movies returned successfully")
}
})
return super.onCreateView(inflater, container, savedInstanceState)
}
}
Scopes tell a component to cache the results of a binding. It has nothing to do with caching instances of any components. As such, you are always creating a new DataComponent, PresentationComponent, and AllMoviesComponent in your fragments' onCreate methods.
In order to reuse the same AllMoviesComponent instance, you need to store it somewhere. Where you store it can depend on your app architecture, but some options include MyApplication itself, the hosting Activity, or in your navigation graph somehow.
Even after fixing this, you can't guarantee that parseMovies has already been called. The Android system could kill your app at any time, including when MoviesDetailFragment is the current fragment. If that happens and the user navigates back to your app later, any active fragments will be recreated, and you'll still get null.