Fix possible RxStore memory leak.

fork-5.53.8
Alex Hart 2022-10-05 12:06:47 -03:00 zatwierdzone przez GitHub
rodzic 4f3910e3ae
commit ee00e931eb
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
21 zmienionych plików z 63 dodań i 13 usunięć

Wyświetl plik

@ -70,6 +70,7 @@ class ViewReceivedGiftViewModel(
override fun onCleared() { override fun onCleared() {
disposables.dispose() disposables.dispose()
store.dispose()
} }
fun setChecked(isChecked: Boolean) { fun setChecked(isChecked: Boolean) {

Wyświetl plik

@ -38,6 +38,7 @@ class ViewSentGiftViewModel(
override fun onCleared() { override fun onCleared() {
disposables.dispose() disposables.dispose()
store.dispose()
} }
class Factory( class Factory(

Wyświetl plik

@ -62,6 +62,7 @@ class DonorErrorConfigurationViewModel : ViewModel() {
override fun onCleared() { override fun onCleared() {
disposables.clear() disposables.clear()
store.dispose()
} }
fun setSelectedBadge(badgeIndex: Int) { fun setSelectedBadge(badgeIndex: Int) {

Wyświetl plik

@ -35,6 +35,7 @@ class ContactChipViewModel : ViewModel() {
disposables.clear() disposables.clear()
disposableMap.values.forEach { it.dispose() } disposableMap.values.forEach { it.dispose() }
disposableMap.clear() disposableMap.clear()
store.dispose()
} }
fun add(selectedContact: SelectedContact) { fun add(selectedContact: SelectedContact) {

Wyświetl plik

@ -59,6 +59,7 @@ import io.reactivex.rxjava3.core.BackpressureStrategy;
import io.reactivex.rxjava3.core.Flowable; import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.rxjava3.core.Observable; import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.disposables.CompositeDisposable; import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.processors.PublishProcessor; import io.reactivex.rxjava3.processors.PublishProcessor;
import io.reactivex.rxjava3.schedulers.Schedulers; import io.reactivex.rxjava3.schedulers.Schedulers;
import io.reactivex.rxjava3.subjects.BehaviorSubject; import io.reactivex.rxjava3.subjects.BehaviorSubject;
@ -135,10 +136,12 @@ public class ConversationViewModel extends ViewModel {
.map(Recipient::resolved) .map(Recipient::resolved)
.subscribe(recipientCache); .subscribe(recipientCache);
conversationStateStore.update(Observable.combineLatest(recipientId.distinctUntilChanged(), conversationStateTick, (id, tick) -> id) Disposable disposable = conversationStateStore.update(Observable.combineLatest(recipientId.distinctUntilChanged(), conversationStateTick, (id, tick) -> id)
.switchMap(conversationRepository::getSecurityInfo) .switchMap(conversationRepository::getSecurityInfo)
.toFlowable(BackpressureStrategy.LATEST), .toFlowable(BackpressureStrategy.LATEST),
(securityInfo, state) -> state.withSecurityInfo(securityInfo)); (securityInfo, state) -> state.withSecurityInfo(securityInfo));
disposables.add(disposable);
BehaviorSubject<ConversationData> conversationMetadata = BehaviorSubject.create(); BehaviorSubject<ConversationData> conversationMetadata = BehaviorSubject.create();
@ -435,6 +438,7 @@ public class ConversationViewModel extends ViewModel {
ApplicationDependencies.getDatabaseObserver().unregisterObserver(messageUpdateObserver); ApplicationDependencies.getDatabaseObserver().unregisterObserver(messageUpdateObserver);
ApplicationDependencies.getDatabaseObserver().unregisterObserver(messageInsertObserver); ApplicationDependencies.getDatabaseObserver().unregisterObserver(messageInsertObserver);
disposables.clear(); disposables.clear();
conversationStateStore.dispose();
EventBus.getDefault().unregister(this); EventBus.getDefault().unregister(this);
} }

Wyświetl plik

@ -33,6 +33,10 @@ class DraftViewModel @JvmOverloads constructor(
val voiceNoteDraft: Draft? val voiceNoteDraft: Draft?
get() = store.state.voiceNoteDraft get() = store.state.voiceNoteDraft
override fun onCleared() {
store.dispose()
}
fun setThreadId(threadId: Long) { fun setThreadId(threadId: Long) {
store.update { it.copy(threadId = threadId) } store.update { it.copy(threadId = threadId) }
} }

Wyświetl plik

@ -41,5 +41,6 @@ class MediaPreviewV2ViewModel : ViewModel() {
override fun onCleared() { override fun onCleared() {
disposables.dispose() disposables.dispose()
store.dispose()
} }
} }

Wyświetl plik

@ -26,6 +26,10 @@ class MediaCaptureViewModel(private val repository: MediaCaptureRepository) : Vi
} }
} }
override fun onCleared() {
store.dispose()
}
fun onImageCaptured(data: ByteArray, width: Int, height: Int) { fun onImageCaptured(data: ByteArray, width: Int, height: Int) {
repository.renderImageToMedia(data, width, height, this::onMediaRendered, this::onMediaRenderFailed) repository.renderImageToMedia(data, width, height, this::onMediaRendered, this::onMediaRenderFailed)
} }

Wyświetl plik

@ -29,5 +29,6 @@ class WhoCanSeeMyPhoneNumberViewModel : ViewModel() {
override fun onCleared() { override fun onCleared() {
disposables.clear() disposables.clear()
store.dispose()
} }
} }

Wyświetl plik

@ -65,6 +65,7 @@ class UsernameEditViewModel extends ViewModel {
protected void onCleared() { protected void onCleared() {
super.onCleared(); super.onCleared();
disposables.clear(); disposables.clear();
uiState.dispose();
} }
void onNicknameUpdated(@NonNull String nickname) { void onNicknameUpdated(@NonNull String nickname) {

Wyświetl plik

@ -66,6 +66,8 @@ class SafetyNumberBottomSheetViewModel(
override fun onCleared() { override fun onCleared() {
disposables.clear() disposables.clear()
destinationStore.dispose()
store.dispose()
} }
fun getIdentityRecord(recipientId: RecipientId): Maybe<IdentityRecord> { fun getIdentityRecord(recipientId: RecipientId): Maybe<IdentityRecord> {

Wyświetl plik

@ -72,6 +72,7 @@ class ShareViewModel(
override fun onCleared() { override fun onCleared() {
disposables.clear() disposables.clear()
store.dispose()
} }
private fun moveToFailedState(throwable: Throwable? = null) { private fun moveToFailedState(throwable: Throwable? = null) {

Wyświetl plik

@ -31,6 +31,7 @@ class ChooseInitialMyStoryMembershipViewModel @JvmOverloads constructor(
override fun onCleared() { override fun onCleared() {
disposables.clear() disposables.clear()
store.dispose()
} }
fun select(selection: DistributionListPrivacyMode): Single<DistributionListPrivacyMode> { fun select(selection: DistributionListPrivacyMode): Single<DistributionListPrivacyMode> {

Wyświetl plik

@ -59,13 +59,14 @@ class StoriesPrivacySettingsViewModel : ViewModel() {
pagingController.set(observablePagedData.controller) pagingController.set(observablePagedData.controller)
store.update(observablePagedData.data.toFlowable(BackpressureStrategy.LATEST)) { data, state -> disposables += store.update(observablePagedData.data.toFlowable(BackpressureStrategy.LATEST)) { data, state ->
state.copy(storyContactItems = data) state.copy(storyContactItems = data)
} }
} }
override fun onCleared() { override fun onCleared() {
disposables.clear() disposables.clear()
store.dispose()
} }
fun setStoriesEnabled(isEnabled: Boolean) { fun setStoriesEnabled(isEnabled: Boolean) {

Wyświetl plik

@ -143,6 +143,7 @@ class StoryViewerViewModel(
override fun onCleared() { override fun onCleared() {
disposables.clear() disposables.clear()
store.dispose()
} }
fun setSelectedPage(page: Int) { fun setSelectedPage(page: Int) {

Wyświetl plik

@ -10,6 +10,10 @@ class StoryVolumeViewModel : ViewModel() {
val state: Flowable<StoryVolumeState> = store.stateFlowable val state: Flowable<StoryVolumeState> = store.stateFlowable
val snapshot: StoryVolumeState get() = store.state val snapshot: StoryVolumeState get() = store.state
override fun onCleared() {
store.dispose()
}
fun mute() { fun mute() {
store.update { it.copy(isMuted = true) } store.update { it.copy(isMuted = true) }
} }

Wyświetl plik

@ -75,6 +75,7 @@ class StoryInfoViewModel(storyId: Long, repository: StoryInfoRepository = StoryI
override fun onCleared() { override fun onCleared() {
disposables.clear() disposables.clear()
store.dispose()
} }
class Factory(private val storyId: Long) : ViewModelProvider.Factory { class Factory(private val storyId: Long) : ViewModelProvider.Factory {

Wyświetl plik

@ -99,6 +99,7 @@ class StoryViewerPageViewModel(
override fun onCleared() { override fun onCleared() {
disposables.clear() disposables.clear()
storyCache.clear() storyCache.clear()
store.dispose()
} }
fun hideStory(): Completable { fun hideStory(): Completable {

Wyświetl plik

@ -51,6 +51,7 @@ class StoryGroupReplyViewModel(storyId: Long, repository: StoryGroupReplyReposit
override fun onCleared() { override fun onCleared() {
disposables.clear() disposables.clear()
store.dispose()
} }
class Factory(private val storyId: Long, private val repository: StoryGroupReplyRepository) : ViewModelProvider.Factory { class Factory(private val storyId: Long, private val repository: StoryGroupReplyRepository) : ViewModelProvider.Factory {

Wyświetl plik

@ -1,8 +1,10 @@
package org.thoughtcrime.securesms.util.rx package org.thoughtcrime.securesms.util.rx
import androidx.annotation.CheckResult
import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.core.Scheduler import io.reactivex.rxjava3.core.Scheduler
import io.reactivex.rxjava3.disposables.Disposable import io.reactivex.rxjava3.disposables.Disposable
import io.reactivex.rxjava3.kotlin.plusAssign
import io.reactivex.rxjava3.processors.BehaviorProcessor import io.reactivex.rxjava3.processors.BehaviorProcessor
import io.reactivex.rxjava3.schedulers.Schedulers import io.reactivex.rxjava3.schedulers.Schedulers
import io.reactivex.rxjava3.subjects.PublishSubject import io.reactivex.rxjava3.subjects.PublishSubject
@ -10,11 +12,14 @@ import io.reactivex.rxjava3.subjects.PublishSubject
/** /**
* Rx replacement for Store. * Rx replacement for Store.
* Actions are run on the computation thread by default. * Actions are run on the computation thread by default.
*
* This class is disposable, and should be explicitly disposed of in a ViewModel's onCleared method
* to prevent memory leaks. Disposing instances of this class is a terminal action.
*/ */
class RxStore<T : Any>( class RxStore<T : Any>(
defaultValue: T, defaultValue: T,
scheduler: Scheduler = Schedulers.computation() scheduler: Scheduler = Schedulers.computation()
) { ) : Disposable {
private val behaviorProcessor = BehaviorProcessor.createDefault(defaultValue) private val behaviorProcessor = BehaviorProcessor.createDefault(defaultValue)
private val actionSubject = PublishSubject.create<(T) -> T>().toSerialized() private val actionSubject = PublishSubject.create<(T) -> T>().toSerialized()
@ -22,20 +27,30 @@ class RxStore<T : Any>(
val state: T get() = behaviorProcessor.value!! val state: T get() = behaviorProcessor.value!!
val stateFlowable: Flowable<T> = behaviorProcessor.onBackpressureLatest() val stateFlowable: Flowable<T> = behaviorProcessor.onBackpressureLatest()
init { val actionDisposable: Disposable = actionSubject
actionSubject .observeOn(scheduler)
.observeOn(scheduler) .scan(defaultValue) { v, f -> f(v) }
.scan(defaultValue) { v, f -> f(v) } .subscribe { behaviorProcessor.onNext(it) }
.subscribe { behaviorProcessor.onNext(it) }
}
fun update(transformer: (T) -> T) { fun update(transformer: (T) -> T) {
actionSubject.onNext(transformer) actionSubject.onNext(transformer)
} }
fun <U> update(flowable: Flowable<U>, transformer: (U, T) -> T): Disposable { @CheckResult
fun <U : Any> update(flowable: Flowable<U>, transformer: (U, T) -> T): Disposable {
return flowable.subscribe { return flowable.subscribe {
actionSubject.onNext { t -> transformer(it, t) } actionSubject.onNext { t -> transformer(it, t) }
} }
} }
/**
* Dispose of the underlying scan chain. This is terminal.
*/
override fun dispose() {
actionDisposable.dispose()
}
override fun isDisposed(): Boolean {
return actionDisposable.isDisposed
}
} }

Wyświetl plik

@ -33,6 +33,7 @@ class RxStoreTest {
// THEN // THEN
subscriber.assertValueAt(0, 1) subscriber.assertValueAt(0, 1)
subscriber.assertNotComplete() subscriber.assertNotComplete()
testSubject.dispose()
} }
@Test @Test
@ -50,6 +51,7 @@ class RxStoreTest {
subscriber.assertValueAt(0, 1) subscriber.assertValueAt(0, 1)
subscriber.assertValueAt(1, 2) subscriber.assertValueAt(1, 2)
subscriber.assertNotComplete() subscriber.assertNotComplete()
testSubject.dispose()
} }
@Test @Test
@ -66,5 +68,6 @@ class RxStoreTest {
// THEN // THEN
subscriber.assertValueAt(0, 2) subscriber.assertValueAt(0, 2)
subscriber.assertNotComplete() subscriber.assertNotComplete()
testSubject.dispose()
} }
} }