my app is crashing due to a null pointer with the recyclerview adapter in the fragment code. I'm using this adapter to generate a card view list but cannot find out what exactly is causing this null pointer. The log has it happening when setting the adapter r.setAdapter(rA) in the faucet class.
Fragment code
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
v = inflater.inflate(R.layout.faucetcards, container, false);
r = (RecyclerView) getActivity().findViewById(R.id.feedRecyclerView);
rA = new RecyclerAdapter(generateCards());
lm = new LinearLayoutManager(getActivity());
r.setAdapter(rA);
r.setLayoutManager(lm);
return v;
}
Update:
My error is with r or setting the adapter. That's where the null pointer occurs even though rA is not null
The Nullpointer at r.set adapter(rA) occurs because r is null.
The reason for r being null is that you tried to find the RecyclerView in the Fragment's parent Activity. But the RecyclerView is contained in the Fragment's layout, which you inflated in the onCreateView method and stored in v. So what you should do instead is:
r = (RecyclerView) v.findViewById(R.id.feedRecyclerView);
Related
Preface
I have a simple app with a viewmodel, a custom UI control, and a TextView. Databinding is setup like this:
LiveData -> control.value -> control.preValue -> TextView
When LiveData is changed, databinding notifies control.value of the new value. control.value setter has a line which also gives the new value to control.preValue. Both properties have calls to their respective databinding listeners to notify databinding that the values have changed and that the UI should be updated. The text value of TextView is bound to control.preValue, so when the listener is notified, the TextView is updated.
This works well at runtime, however there is a problem at initialization.
The Problem
When the UI is first constructed, the LiveData value is not correctly propagated to the TextView. This is because the listeners have not yet been created by the android databinding library, so when control.preValue is set by control.value's setter, the listener is still null.
Diving deeper into executeBindings we can see the cause of the problem.
executeBindings is a function which is part of the *BindingImpl file automatically generated by the databinding library based on the Binding Adapters I have defined. It is responsible for initializing databinding, e.g. creating listeners, registering livedatas, and setting initial values to the UI.
executeBindings starts like this. It initializes variables for all the databound values.
#Override
protected void executeBindings() {
long dirtyFlags = 0;
synchronized(this) {
dirtyFlags = mDirtyFlags;
mDirtyFlags = 0;
}
java.lang.Integer viewmodelBpmGetValue = null;
androidx.lifecycle.MutableLiveData<java.lang.Integer> viewmodelBpm = null;
int bpmPickerPreValue = 0;
androidx.lifecycle.MutableLiveData<java.lang.Boolean> viewmodelPlaying = null;
java.lang.String integerToStringBpmPickerPreValue = null;
int androidxDatabindingViewDataBindingSafeUnboxViewmodelBpmGetValue = 0;
com.okos.metronome.MetViewModel viewmodel = mViewmodel;
Next, it gets the value of control.preValue property and stores it in the earlier created variable. This is already the core of the problem. At this point control.preValue is still at the default value that is defined in the control's definition class, not the LiveData value which will be assigned to it a bit later.
if ((dirtyFlags & 0x18L) != 0) {
// read bpmPicker.preValue
bpmPickerPreValue = bpmPicker.getPreValue();
// read Integer.toString(bpmPicker.preValue)
integerToStringBpmPickerPreValue = java.lang.Integer.toString(bpmPickerPreValue);
}
Next we get the LiveData value from the viewmodel and register it with databinding
if ((dirtyFlags & 0x15L) != 0) {
if (viewmodel != null) {
// read viewmodel.bpm
viewmodelBpm = viewmodel.getBpm();
}
updateLiveDataRegistration(0, viewmodelBpm);
if (viewmodelBpm != null) {
// read viewmodel.bpm.getValue()
viewmodelBpmGetValue = viewmodelBpm.getValue();
}
// read androidx.databinding.ViewDataBinding.safeUnbox(viewmodel.bpm.getValue())
androidxDatabindingViewDataBindingSafeUnboxViewmodelBpmGetValue = androidx.databinding.ViewDataBinding.safeUnbox(viewmodelBpmGetValue);
}
Here it sets control.value to the value of the LiveData in the first if block. This line will trigger the control.value setter, which will set control.preValue, and those setters will both try to call their respective onChange listeners but they will be null because executeBindings hasn't created them yet. They are created in the 2nd if block.
if ((dirtyFlags & 0x15L) != 0) {
// api target 1
this.bpmPicker.setValue(androidxDatabindingViewDataBindingSafeUnboxViewmodelBpmGetValue);
}
if ((dirtyFlags & 0x10L) != 0) {
// api target 1
com.okos.metronome.view.DialPickerBindingAdapter.setPreValueListener(this.bpmPicker, (com.okos.metronome.view.PrePickerBase.OnValueChangeListener)null, bpmPickerpreValueAttrChanged);
com.okos.metronome.view.DialPickerBindingAdapter.setValueListener(this.bpmPicker, (com.okos.metronome.view.PrePickerBase.OnValueChangeListener)null, bpmPickervalueAttrChanged);
}
Finally, the value of the TextView is set, but it is set to the original value of preValue which we cached in a variable in the very first if block. **Not the new value which has been updated to preValue from the LiveData since then.
if ((dirtyFlags & 0x18L) != 0) {
// api target 1
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.tvBpmDisplay, integerToStringBpmPickerPreValue);
}
This seems like an oversight in the databinding library, and I wonder if anyone has any ideas of getting around this? The fix seems pretty simple to just move the first if block in executeBindings down so integerToStringBpmPickerPreValue is set after the value has been set from LiveData, but because executeBindings is automatically generated, I can't do that. There are some ways of changing the order of execution in executeBindings, like which order the bindings are defined in the xaml, but none of that affects the parts I want to change.
I've got the problem with creating multiple fragment instances in OnCreate. When i close the app using Home button and I will return to the app, the fragment instance is created one more time. How can I prevent this?
fragment = FragmentMain.newInstance(intent.extras?.getSerializable(DATA_MAIN)).also {
supportFragmentManager.beginTransaction()
.add(frameLayout.id, it, FragmentMain::class.java.simpleName)
.addToBackStack(FragmentMain::class.java.simpleName)
.commit()
}
This is expected behavior, as Android recreates the fragments after process death that are added to the fragment manager.
You're just also adding a 2nd new fragment on top of the one created by Android, which, you probably don't want to do.
fragment = when {
savedInstanceState == null -> FragmentMain.newInstance(intent.extras?.getSerializable(DATA_MAIN)).also {
supportFragmentManager.beginTransaction()
.add(frameLayout.id, it, FragmentMain::class.java.simpleName)
.addToBackStack(FragmentMain::class.java.simpleName)
.commit()
}
else -> supportFragmentManager.findFragmentByTag(FragmentMain::class.java.simpleName)
}
My Android app uses a RecyclerView to display a managed RealmList contained in a RealmObject. If the RealmObject is deleted I get the following error:
java.lang.IllegalStateException: Access to invalidated List object
The error occurs in the this section of the adapter (which extends RecyclerView.Adapter):
#Override
public int getItemCount() {
if (mEventList != null) {
return mEventList.size(); //Error occurs here
} else {
return 0;
}
}
EventListAdapter(RealmList<TodoItem> todoItems, int colorGroup, OnStartDragListener dragStartListener) {
this.mEventList = todoItems;
this.colorGroup = colorGroup;
this.mDragStartListener = dragStartListener;
}
It occurs in conjunction with the following code to delete the RealmObject containing the RealmList. The error occurs before the Activity is closed and info is returned to its parent Activity
case R.id.navigation_delete:
deleteGroup();
sendIntentInfoBack(Constants.RESULT_CHANGED, createIntentBundle());
return true;
private void deleteGroup() {
mRealm.executeTransaction(realm -> {
RealmList<TodoItem> eventsToDelete = mCurrentTodoList.getTodos();
eventsToDelete.deleteAllFromRealm();
RealmResults<TodoList> noteToDelete = mRealm.where(TodoList.class).equalTo(TodoList.PK, mCurrentTodoList.getPK()).findAll();
noteToDelete.deleteAllFromRealm();
});
}
The user must be able to delete the RealmObject containing the RealmList.
My Questions:
How do I handle the RealmList being invalidated? I've tried assigning the RecyclerView data source to be null, then doing notifyDataSetChanged() on the adapter before deleting the RealmObject, but get the same error.
Do I disable the RecyclerView? If so, how?
Do I create an empty or throw-away RealmList, then assign a new adapter to the RecyclerView with this as the data source, then delete the RealmObject?
Do I close the Activity containing the RecyclerView and just handle deleting the RealmObject in the parent Activity?
Thanks for your consideration and time.
my MainActivity contains a ViewPager that loads 4 fragments, each fragment should load lots of data from the server.
so when my app wants to be run for the first time, it almost takes more than 3 seconds and the other times(for example, if you exit the app but not clean it from your 'recently app' window and reopen it) it takes almost 1 second.
while it is loading, it shows a white screen.
is there any way instead of showing a white screen till data become ready, I show my own image?
something like the splash page?
If you do long-running actions on the main thread, you risk getting an ANR crash.
Your layout for each fragment should have a loading view that is initially visible, and your data view. Something like this:
(not code)
FrameLayout
loading_view (can show a progress spinner or something, size is match parent)
content_view (probably a RecyclerView, initial visibility=GONE, size is match parent)
/FrameLayout
You need to do your long running action on a background thread or coroutine, and then swap the visibility of these two views when the data is ready to show in the UI.
You should not be directly handling the loading of data in your Fragment code, as Fragment is a UI controller. The Android Jetpack libraries provide the ViewModel class for this purpose. You would set up your ViewModel something like this. In this example, MyData could be anything. In your case it's likely a List or Set of something.
class MyBigDataViewModel(application: Application): AndroidViewModel(application) {
private val _myBigLiveData = MutableLiveData<MyData>()
val myBigLiveData: LiveData<MyData>() = _myBigLiveData
init {
loadMyBigData()
}
private fun loadMyBigData() {
viewModelScope.launch { // start a coroutine in the main UI thread
val myData: MyData = withContext(Dispatchers.Default) {
// code in this block is done on background coroutine
// Calculate MyData here and return it from lambda
// If you have a big for-loop, you might want to call yield()
// inside the loop to allow this job to be cancelled early if
// the Activity is closed before loading was finished.
//...
return#withContext calculatedData
}
// LiveData can only be accessed from the main UI thread so
// we do it outside the withContext block
_myBigLiveData.value = myData
}
}
}
Then in your fragment, you observe the live data to update the UI when it is ready. The below uses the fragment-ktx library, which you need to add to your project. You definitely should read the documentation on ViewModel.
class MyFragment: Fragment() {
// ViewModels should not be instantiated directly, or they won't be scoped to the
// UI life cycle correctly. The activityViewModels delegate handles instantiation for us.
private val model: MyBigDataViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
model.myBigLiveData.observe(this, Observer<MyData> { myData ->
loading_view.visibility = View.GONE
content_view.visibility = View.VISIBLE
// use myData to update the view content
})
}
}
RecyclerView with each item as an Exoplayer throws out of memory exception even after releasing the player in onViewRecycled() method of adapter.
what i can do to resolve this issue?
i have gone through other posts about this issue, out of that i tried this link but this also is not working.
And i have already increased the heapSize in manifest.
snippet of code from adapter:
override fun onBindViewHolder(holder: MemoryHomeHolder, position:Int)
{
val memory = memoryList[position]
holder.bind(memory)
holder.itemMemoryListBinding.playerView?.useController = true
holder.itemMemoryListBinding.playerView?.showController()
val path: memory.path
val player = ExoPlayerFactory.newSimpleInstance(
context,
DefaultTrackSelector(),
DefaultLoadControl()
)
val dataSourceFactory = DefaultDataSourceFactory(
context,
Util.getUserAgent(context, AppConstants.OFH)
)
val source = ProgressiveMediaSource.Factory(dataSourceFactory) .createMediaSource(Uri.parse(path))
holder.itemMemoryListBinding.playerView?.player = player
holder.itemMemoryListBinding.playerView?.controllerHideOnTouch = false
holder.itemMemoryListBinding.playerView?.controllerShowTimeoutMs = -1
holder.itemMemoryListBinding.playerView?.resizeMode =
AspectRatioFrameLayout.RESIZE_MODE_FILL
holder.itemMemoryListBinding.playerView?.exo_progress?.visibility
= View.GONE
(holder.itemMemoryListBinding.playerView?.player as
SimpleExoPlayer?)?.prepare(source)
(holder.itemMemoryListBinding.playerView?.player as
SimpleExoPlayer?)?.repeatMode = Player.REPEAT_MODE_ALL
activePlayerHolders.add(holder)
Log.d(TAG, "holder init for position = $position")
}
override fun onViewRecycled(holder: MemoryHomeHolder) {
super.onViewRecycled(holder)
holder.itemMemoryListBinding.playerView?.player?.release()
}
I want the RecyclerView to have Exoplayer as an item in every holder and it should not throw out of memory exception
I was not releasing the player properly on onViewRecycled() method, which created out of memory exception.
you can simply release the player of the holder being released as below:
holder?.playerView?.player?.release()
This fixed the issue.