Dlaczego istnieje oddzielna podklasa MutableLiveData w usłudze LiveData?

97

Wygląda na to, że MutableLiveDataróżni się od LiveDatatylko upublicznieniem metod setValue()i postValue(), podczas LiveDatagdy są one chronione.

Jakie są powody, dla których warto utworzyć osobną klasę dla tej zmiany, a nie po prostu zdefiniować te metody jako publiczne LiveDatasame w sobie?

Ogólnie rzecz biorąc, czy taka forma dziedziczenia (zwiększenie widoczności niektórych metod jest jedyną zmianą) jest dobrze znaną praktyką i jakie scenariusze mogą się przydać (zakładając, że mamy dostęp do całego kodu)?

Alexander Kulyakhtin
źródło
11
jest to decyzja projektowa. LiveDatajest niezmienny, ponieważ klient nie może zmienić stanu wewnętrznego, dlatego jest bezpieczny
wątkowo

Odpowiedzi:

139

W LiveData - Android dokumentacji dla programistów , można zobaczyć, że dla LiveData, setValue()i postValue()metody nie są jawne.

Podczas gdy w MutableLiveData - Android Developer Documentation widać, że MutableLiveDatarozszerza się LiveDatawewnętrznie, a także dwie magiczne metody LiveDatapublicznie dostępne w tym i są to setValue()& postValue().

setValue(): ustaw wartość i wyślij wartość do wszystkich aktywnych obserwatorów, musi być wywołane z głównego wątku .

postValue(): wyślij zadanie do głównego wątku, aby nadpisać wartość ustawioną przez setValue(), musi być wywołane z wątku w tle .

Więc LiveDatajest niezmienna . MutableLiveDatato LiveDataco jest zmienny i bezpieczny wątku .

Sneh Pandya
źródło
39
Tak naprawdę nie jest tak, że LiveData jest niezmienna, po prostu nie można jej modyfikować poza klasą ViewModel. Klasa ViewModel może ją dowolnie modyfikować (np. ViewModel czasomierza). Użyłbyś MutableLiveData, jeśli chcesz zmodyfikować go poza klasą ViewModel.
Elliptica
2
Weźmy ten scenariusz, aplikację ze wzorcem repozytorium (serwer + pomieszczenie), w którym Pokój jest jedynym źródłem prawdy. Aplikacja pobiera dane tylko z pokoju, a Room pobiera aktualizację z serwera. Czy mutableLiveData musi być używany, ponieważ można użyć danych z pokoju aktualizacji serwera lub LiveData?
Dr4ke the b4dass
5
LiveData jest abstrakcyjna, więc nie można bezpośrednio utworzyć obiektu LiveData bez jego rozszerzania. MutableLiveData rozszerza LiveData.
Serdar Samancıoğlu,
1
Linki do LiveData i MutableLiveData prowadzą bezpośrednio do przestarzałej dokumentacji. Dlaczego, kiedy zasugerowałem zmianę z rzeczywistymi linkami, została ona odrzucona?
Daniel
1
@Daniel nie jest pewien, dlaczego został odrzucony przez innych recenzentów w kolejce recenzji. Zatwierdziłem zmianę, dzięki! :)
Sneh Pandya,
10

To jest cały MutableLiveData.javaplik:

package androidx.lifecycle;
/**
 * {@link LiveData} which publicly exposes {@link #setValue(T)} and {@link #postValue(T)} method.
 *
 * @param <T> The type of data hold by this instance
*/
@SuppressWarnings("WeakerAccess")
public class MutableLiveData<T> extends LiveData<T> {
    @Override
    public void postValue(T value) {
        super.postValue(value);
    }
    @Override
    public void setValue(T value) {
        super.setValue(value);
    }
}

Więc tak, różnica pojawia się tylko poprzez udostępnianie postValuei setValueupublicznianie.

Jednym z przypadków użycia, które mogę sobie przypomnieć, jest hermetyzacja przy użyciu właściwości Backing w Kotlinie. Możesz ujawnić LiveDataswój fragment / aktywność (kontroler UI), nawet jeśli możesz MutableLiveDatamanipulować w swojej ViewModelklasie.

    class TempViewModel : ViewModel() {
        ...
        private val _count = MutableLiveData<Int>()
        val count: LiveData<Int>
            get() = _count
        public fun incrementCount() = _count.value?.plus(1)
        ...
    }

W ten sposób kontroler interfejsu użytkownika będzie mógł obserwować tylko wartości bez możliwości ich edycji. Oczywiście kontroler interfejsu użytkownika może edytować wartości przy użyciu publicznych metod, TempViewModeltakich jak incrementCount().

Uwaga : aby wyjaśnić zmienne / niezmienne zamieszanie -

data class User(var name: String, var age: Int)

class DemoLiveData: LiveData<User>()

var demoLiveData: LiveData<User>? = DemoLiveData()

fun main() {
    demoLiveData?.value = User("Name", 23) // ERROR
    demoLiveData?.value?.name = "Name" // NO ERROR
    demoLiveData?.value?.age = 23  // NO ERROR
}
Ananth
źródło
Co to jest _score?
Igor Ganapolsky
0

MutableLiveData rozszerza się z LiveData. Metody chronione LiveData mogą być adresowane tylko przez własne lub podklasy. W tym przypadku MutableLiveData będąca podklasą LiveData może uzyskać dostęp do tych chronionych metod.

To, co chciałbyś zrobić, to obserwować na instancji i zobaczyć, czy są jakieś zmiany. Ale jednocześnie nie chcesz, aby ktokolwiek z zewnątrz zmienił obserwowaną instancję. W pewnym sensie stwarza to problem, ponieważ chciałbyś mieć obiekt, który jest i można go zmieniać, aby aktualizować każdy nowy status i nie można go zmieniać, aby upewnić się, że nikt, kto nie powinien, nie może zaktualizować tej instancji. Te dwie funkcje są ze sobą sprzeczne, ale można je rozwiązać, tworząc dodatkową warstwę.

Więc to, co robisz, to rozszerzanie swojej klasy, LiveData, o klasę, która ma dostęp do jej metod. Warstwa podrzędna, w tym przypadku MutableLiveData, ma dostęp do chronionych metod swojego rodzica (/ super).

Teraz zaczynasz tworzyć instancje i tworzymy instancję obserwatora MutableLiveData. Jednocześnie tworzysz instancję LiveData odwołującą się do tej samej instancji. Ponieważ MutableLiveData rozszerza LiveData, każda instancja MutableLiveData jest obiektem LiveData i dlatego zmienna LiveData może się do niej odwoływać.

Teraz sztuczka jest prawie zakończona. Ujawniasz tylko instancję LiveData, nikt nie może używać jej chronionych metod, ani nie może przesyłać jej do super (być może w czasie kompilacji, ale nie działałaby: błąd RunTime). Zachowujesz prywatność rzeczywistej instancji podklasy, więc mogą ją zmienić tylko właściciele instancji, używając metod instancji.

//create instance of the sub class and keep this private
private val _name: MutableLiveData<String> = MutableLiveData<String>()
//create an instance of the super class referring to the same instance
val name: LiveData<String> = _name
//assign observer to the super class, being unable to change it
name.value.observe(.....)

Teraz superklasa powiadamia o zastosowaniu jakichkolwiek zmian.

//change the instance by using the sub class
_name.postValue(...)
//or _name.setValue(...)

Cytat blokowy Ogólnie rzecz biorąc, czy taka forma dziedziczenia (zwiększenie widoczności niektórych metod jest jedyną zmianą) jest dobrze znaną praktyką i jakie scenariusze mogą się przydać (zakładając, że mamy dostęp do całego kodu)?

Tak, jest to dość dobrze znane i ten opisany powyżej jest częstym scenariuszem. Usuń wzorzec obserwatora i po prostu utwórz go w formie set / get, przyniosłoby to równie duże korzyści. W zależności od tego, gdzie go wdrożysz, ostatecznie nie będzie żadnych złotych zasad.

Khamaseen
źródło