Exoplayer in recyclerView gives out of memory exception - android-recyclerview

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.

Related

Take snapshot of MutableStateFlow that doesn't change with the state in Jetpack Compose

I'm trying to make an editor app that allows you to undo/redo changes. I want to achieve that by storing the ui state in stacks (ArrayDeque) and pop them back once the user hits undo/redo. But every time I stored the state in stack, after I made change to the state, the value in the stack is changed as well.
Is there a way to snapshot a state that won't be affected by future changes in the state flow?
My code looks something like this:
Data Class
class Data () {
var number: Int = 0
fun foo()
}
State Class
data class UiState(
val dataList: MutableList<Data> = mutableListOf(),
)
ViewModel
class UiViewModel : ViewModel() {
private val _uiState = MutableStateFlow(UiState())
private val undoStack: ArrayDeque<UiState> = ArrayDeque()
fun makeChangeToState() {
saveStateToUndoStack(_uiState.value)
Log.i("Test", undoStack.last().dataList[0].number) // 0
val dataList= _uiState.value.dataList.toMutableStateList()
dataList[0].number = 1
_uiState.update { currentState ->
currentState.copy(
dataList = dataList,
)
}
Log.i("Test", undoStack.last().dataList[0].number) // 1 because the _uiState changed
}
fun undo() {
val lastState = undoStack.last()
// Won't work because the data in lastState has already been updated with _uiState
_uiState.update { lastState }
}
}
Things I've tried:
Use _uiState.value.copy
Call saveStateToUndoStack(uiState: UiState) from Composable functions and pass in the viewModel.uiState.collectAsState()
Both doesn't seem to work, I play around for a few hours but don't have a clue.
The reason why the old value got updated is because all the objects in the list are references, not related to MutableStateFlow. I just need a deep copy of the list, see this post here:
https://www.baeldung.com/kotlin/deep-copy-data-class
Another thread worth reading: Deep copy of list with objects in Kotlin

Why method not return anything after dao method?

My code stop working after call dao method, if I use GlobalScope code working, but then LiveData not update changes
override suspend fun addNewTask(title: String,priority : Int,date: Date): Boolean {
var isSuccess = false
val newTask = hashMapOf(
"title" to title,
"priority" to priority,
"task_points" to converterListOfListToJson(listOf()),
"date" to Timestamp(date)
)
val user: User? = userDao.getCurrentUser()
val newTaskList = user?.tasks?.toMutableList()
val generatedDoc = db.collection(CollectionNames.tasks).document()
val userTasks = db.collection(CollectionNames.users).document(user!!.id)
generatedDoc.set(newTask)
.addOnSuccessListener { isSuccess = true }
.addOnFailureListener { Log.e(TAG, "Error writing document") }.await()
userTasks.update(
"tasks", FieldValue.arrayUnion(generatedDoc.id)
).await()
newTaskList?.add(generatedDoc.id)
taskDao.insert(Task(generatedDoc.id, title, date, listOf(), priority.toLong())) //after code out
user.tasks = newTaskList!!.toList()
userDao.updateUser(user)
return isSuccess
}
This call of this method, addNewTask full complete but after code just end method
fun createTask(title : String, priority : String,date: Date) {
viewModelScope.launch{
tasksUseCase.addNewTask(title, priority, date)
tasksUseCase.updateTaskFromLocalDB().collect { //it is not called
_taskList.postValue(it)
}
}
}
In DAO I just use annotation update and insert
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(task: Task)
My function call in dialog fragment on click, but with viewModel of parent fragment, function deadlock without errors
.addOnSuccessListener { isSuccess = true } will change the variable to true some time in the future, when the background work is done. Your code that uses isSuccess is called before it has a chance to finish the background work.
When you used GlobalScope, it probably used the default dispatcher Dispatchers.Default, so you had a race condition and in some cases it could succeed.
When you used viewModelScope, it uses Dispatchers.Main, so there is no possibility of the result listener being called before the end of this suspend function unless there is some other suspending call in between.
What you need to do to fix it is run your task in a synchronous, suspending way instead of an asynchronous way. Since I don't know what class generatedDoc is, I can't help with that. Many libraries include extension suspend function, usually named await(), that let you get the result synchronously. If they don't provide that, you can write your own using suspendCancellableCoroutine. There are many other questions about that on here that you can search for to see how to use it.
The problem was that the viewModel was a DialogFragment which was destroyed after the method was called, due to which the ViewModelScope also ceased to exist and the method crashed in the middle

Android RecyclerView NotifyDataSetChanged with LiveData

When instantiating the ListAdapter for my RecyclerView, I call:
viewModel.currentList.observe(viewLifecycleOwner){
adapter.submitList(it)
}
which is what I've seen done in the Android Sunflower project as the proper way to submit LiveData to a RecyclerView. This works fine, and when I add items to my database they are updated in the RecyclerView. However, if I write
viewModel.currentList.observe(viewLifecycleOwner){
adapter.submitList(it)
adapter.notifyDataSetChanged()
}
Then my adapters data observer always lags behind. I call
adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver(){
override fun onChanged() {
super.onChanged()
if (adapter.currentList.isEmpty()) {
empty_dataset_text_view.visibility = View.VISIBLE
} else {
empty_dataset_text_view.visibility = View.GONE
}
}
})
And empty_dataset_text_view appears one change after the list size reached zero, and likewise it disappears one change after the list size is non-zero.
Clearly, the observer is running the code before it has submitted it which in this case is LiveData queried from Room. I'd simply like to know what the workaround is for this: is there a way to "await" a return from my LiveData?

how to have loading in Kotlin

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
})
}
}

Why not correct check "is"?

Here Kotlin's snippet:
fun withBackgroundColorResId(#IdRes expectedId: Int): Matcher<Any> {
return object : BoundedMatcher<Any, Any>(Any::class.java) {
override fun matchesSafely(view: Any): Boolean {
if (view !is View || view !is ViewGroup) {
Log.w(TAG, "withBackgroundColorResId_incorrect_type, view = $view")
return false
}
val currenColor = (view.background.current as ColorDrawable).color
val expectedColor = ContextCompat.getColor(view.context, expectedId)
return currenColor == expectedColor
}
override fun describeTo(description: Description) {
description.appendText("textView with background color resId: ")
description.appendValue(context.getResources().getString(expectedId))
}
}
}
and usage:
onView(withRecyclerView(tradersRecyclerView).atPositionOnView(traderCheckPos, pauseTextView)).check(matches(withBackgroundColorResId(trade_not_running_color)))
and here logcat:
06-05 10:25:12.787 I/ViewInteraction(22053): Checking 'MatchesViewAssertion{viewMatcher=textView with background color resId: "#ffbde6ff"}' assertion on view RecyclerView with id: com.myproject.debug:id/tradersRecyclerView at position: 0
06-05 10:25:12.787 W/com.myproject.custom.matcher.CustomMatchers(22053): withBackgroundColorResId_incorrect_type, view = androidx.appcompat.widget.AppCompatTextView{a412c35 V.ED..C.. ........ 0,0-288,288 #7f0800c9 app:id/pauseTextView}
06-05 10:25:12.794 D/com.myproject.activity.TradersActivityTest(22053): afterEach
06-05 10:25:12.794 I/MockWebServer(22053): MockWebServer[8081] done accepting connections: Socket closed
06-05 10:25:12.825 D/LifecycleMonitor(22053): Lifecycle status change: com.myproject.ui.activity.TradersActivity#2964795 in: PAUSED
06-05 10:25:12.825 D/LifecycleMonitor(22053): running callback: androidx.test.rule.ActivityTestRule$LifecycleCallback#9705558
06-05 10:25:12.825 D/LifecycleMonitor(22053): callback completes: androidx.test.rule.ActivityTestRule$LifecycleCallback#9705558
as you can see the object "view" has type androidx.appcompat.widget.AppCompatTextView. As we known AppCompatTextView is extends from View
But why it print
withBackgroundColorResId_incorrect_type, view = androidx.appcompat.widget.AppCompatTextView
?
In Kotlin's documentation is states:
checks that a value has a certain type
So according to Your code, You ask in a way
is my view instance of X?
Following this line You get:
is view instanceof View? Answer is yes.
is view instanceof ViewGroup? Answer is no.
Single views extends mainly by View class. ViewGroups are mostly used in more complex layouts like LinearLayout,FrameLayout, etc.
Concluding the results, You have:
true||false = true
The problem is with your if condition, as the view is not a ViewGroup the condition is always true thus will print the LOG.
if (view is View) {
Log.w(TAG, "withBackgroundColorResId_incorrect_type, view = $view")
return false
}
Please, try with this one and see the outcome.