简体   繁体   中英

ViewModel manipulation in MVVM Architecture

My Problem is, that i need to preprocess data from the Room Database before showing it in my View.

Therefore here some Context of my Application:

Im programming a "record card" android application. Therefore i have a Room Database where all my record-cards are stored. The Information in the Entitiy is:

@Entitiy:
- ID
- Question
- Answer
- Topic
- Boxnumber (Box depends on how often i was right with my Answer)

Around the Entity i have the normal Room Setup, i found in several tutorials, with: Dao, Database, Repository . Furthermore i have a ViewModel connected to the Repository . And a View for showing the current Question connected to the ViewModel .

My Idea:

My Idea was that the ViewModel can hold LiveData of all needed Cards (with a specific Topic for example). I need some algorithm on that data which will select me my next Card i need for the View . This depends on: How much cards have different boxnumbers, which cards was the last 10 Questions, etc.

My Problem:

In my ViewModel i recieve LiveData from the repository . Every Tutorial shows only displaying the Room-Database Data with an View. But i need to do some preprocessing inside the ViewModel . Accessing the LiveData with .getValue() is not working and only returning null objects. Observing the Data in the ViewModel is also not working, because you need an Activity for this.

I don't know where i should place the algorithm working with the data from the database. I don't want to put it in the View because i want to hold the current algorithm parameters inside the ViewModel .

Some Code to understand my Program better:

@Dao

@Query("SELECT * FROM Karteikarte WHERE BoxnummerMixed = :Boxnummer")
LiveData<List<Karteikarte>> getKarteikartenInBoxMixed(int Boxnummer);

@Query("SELECT * FROM Karteikarte WHERE BoxnummerTopic = :Boxnummer AND Thema = :thema")
LiveData<List<Karteikarte>> getKarteikartenInBoxTopic(int Boxnummer, int thema);

Repository

public LiveData<List<Karteikarte>> getKarteikarteInBoxMixed(int boxnummer){
    return karteikarteDao.getKarteikartenInBoxMixed(boxnummer);
}

public LiveData<List<Karteikarte>> getKarteikarteInBoxTopic(Thema thema, int boxnummer){
    return karteikarteDao.getKarteikartenInBoxTopic(thema.ordinal(), boxnummer);
}

ViewModel

public LearningViewModel(Application application){
    super(application);
    repository = new KarteikarteRepository(application);
}

//This method will be called from the View at onCreate
public void initLearning(){
    allCards = repository.getKarteikarteInBoxMixed(1);
}

//This method will be called from the View
public Karteikarte nextCard() {
        // here will be a more complex algorithm
        Random random = new Random();
        List<Karteikarte> list = allCards.getValue();
        return list.get(random.nextInt(list.size()));
}

You can use Transformations to modify data in viewModel/Repository before going to Fragment or Activity

Transforamtions.map creates a New LiveData which will observe the liveData that you pass and when ever there is new data, The data will be passed though the function you provide before sending it to the Fragment or activity

private val liveDataFromRoomDatabase: LiveData<RecordCard> = getFromRoomDatabase()
val recordCardLiveData:LiveData<RecordCard>
    = Transformations.map(liveDataFromRoomDatabase){ recordCard ->
    // do all the changes you need here and return record card in this function
    recordCard
}

For more complex use cases you can use MediatorLiveData

Here i have used Transformations.switchMap() and Transformations.map()

For detail understanding of Transformations and MediatorLiveData, you can watch this Video from Android Dev Summit '18 - Fun with LiveData

private MutableLiveData<Integer> boxNumberLiveData = new MutableLiveData<>();
private final LiveData<List<Karteikarte>> allCardsLiveData;

//Observe this from Fragment or Activity
public final LiveData<Karteikarte> karteikarteLiveData;

LearningViewModel(){

    // when ever you change box number below function is called and list of Karteikarte will be updated
    allCardsLiveData = Transformations.switchMap(boxNumberLiveData, new Function<Integer, LiveData<List<Karteikarte>>>() {
        @Override
        public LiveData<List<Karteikarte>> apply(Integer number) {
            if(number == null)return null;
            return repository.getKarteikarteInBoxMixed(1);
        }
    });

    // when ever list of Karteikarte is changed, below function will be called and a random Karteikarte will be selected
    karteikarteLiveData = Transformations.map(allCardsLiveData, new Function<List<Karteikarte>, Karteikarte>() {
        @Override
        public Karteikarte apply(List<Karteikarte> list) {
            return getRandomKarteikarte(list);
        }
    });

}

public void initLearning(){
    //Enter box number here
    // you can modify box number anytime you want, everything will change respectively
    boxNumberLiveData.setValue(1);
}


private Karteikarte getRandomKarteikarte(@Nullable List<Karteikarte> list){
    if(list == null)return null;
    Random random = new Random();
    return list.get(random.nextInt(list.size()));
}

If you want to get the next random Karteikarte. The best way to implement this is to have a extra field in database.

@Entitiy:
- ID
- Question
- Answer
- Topic
- Boxnumber (Box depends on how often i was right with my Answer)
- isShown(boolean)

while querying the data you need to add extra condition that isShown is false so when you want to load next Karteikarte, you just need to update the current Karteikarte's isShown field to true

if you want to re-use already shown Karteikarte, you can just reset isShown values to false when you want start the process

对于实时数据,您需要使用LifeCycleOwner(您需要在该活动或存储库或vm中实施)。检查google中的示例代码以获取实时数据

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM