kopia lustrzana https://github.com/ryukoposting/Signal-Android
Fix possible RxStore memory leak.
rodzic
4f3910e3ae
commit
ee00e931eb
|
@ -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) {
|
||||||
|
|
|
@ -38,6 +38,7 @@ class ViewSentGiftViewModel(
|
||||||
|
|
||||||
override fun onCleared() {
|
override fun onCleared() {
|
||||||
disposables.dispose()
|
disposables.dispose()
|
||||||
|
store.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
class Factory(
|
class Factory(
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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) }
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,5 +41,6 @@ class MediaPreviewV2ViewModel : ViewModel() {
|
||||||
|
|
||||||
override fun onCleared() {
|
override fun onCleared() {
|
||||||
disposables.dispose()
|
disposables.dispose()
|
||||||
|
store.dispose()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,5 +29,6 @@ class WhoCanSeeMyPhoneNumberViewModel : ViewModel() {
|
||||||
|
|
||||||
override fun onCleared() {
|
override fun onCleared() {
|
||||||
disposables.clear()
|
disposables.clear()
|
||||||
|
store.dispose()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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> {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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> {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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) }
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Ładowanie…
Reference in New Issue