Search code examples
android-roomandroid-databaseandroid-viewmodelviewmodelproviders

How room database and viewmodel works?


I have a students_table and there are stored students of different levels. I want to display students by one level and hide other levels. I select student to show like this:

    if (id == R.id.beginners) {
        stLvl = 0;
    }else if (id == R.id.intermediate) {
        stLvl = 1;
    }else if (id == R.id.advanced) {
        stLvl = 2;
    }else if (id == R.id.high_level) {
        stLvl = 3;
    }

    showStud();

And here it is showStud ();

public void showStud() {

    RecyclerView recyclerView = findViewById(R.id.recycler_view);
    recyclerView.setLayoutManager(new LinearLayoutManager(this));
    recyclerView.setHasFixedSize(true);

    final StudentAdapter adapter = new StudentAdapter();
    recyclerView.setAdapter(adapter);

    setStLvl(stLvl);

    if (stLvl == 0) {

        studentViewModel = ViewModelProviders.of(this).get(StudentViewModel.class);

        studentViewModel.getAllStudents().observe(this, new Observer<List<Student>>() {
            @Override
            public void onChanged(@Nullable List<Student> students) {
                // update RecyclerView
                adapter.submitList(students);
            }
        });

    }else {

        studentViewModel = ViewModelProviders.of(this).get(StudentViewModel.class);

        studentViewModel.getStudentsByLevel().observe(this, new Observer<List<Student>>() {
            @Override
            public void onChanged(@Nullable List<Student> students) {
                // update RecyclerView
                adapter.submitList(students);
            }
        });

    }
}

First time when the code run it works perfect, no matter the value of stLvl, but when I change it's value is not displaying what I want, or nothing at all. I think the problem is at this line:

studentViewModel = ViewModelProviders.of(this).get(StudentViewModel.class);

First time it runs, it is working ok, going to StudentViewModel.class doing what is supposed to do, but second time just jumps to next line of code, without going to StudentViewModel.class. What am I doing wrong? Thank you in advance!


Solution

  • First of all, reading this Guide to app architecture will help you get the general idea of how these architectural components should work together. The rule of thumb is,

    each component depends only on the component one level below it.

    This also means that each component should not depend on the components above it. For example, the repository should not depend on neither ViewModels nor Activities. Your code can be refactored in this way:

    StudentRepository:

    private StudentDao studentDao;
    
    // public int stLevel; 
    
    // public void setStLvl() {  // Do not read view components. Do not store their states.
    //     MainActivity mainActivity = new MainActivity();
    //     stLevel = mainActivity.getStLvl();
    // }
    
    public StudentRepository(Application application) {
        AppDatabase database = AppDatabase.getInstance(application);
        studentDao = database.studentDao();
    
        // setStLvl();
    }
    .
    .
    .
    public LiveData<List<Student>> getAllStudents() {
        return studentDao.getAllStudents();
    }
    
    public LiveData<List<Student>> getStudentsByLevel(int stLevel) {
        return studentDao.getStudentsByLevel(stLevel);
    }
    

    In the above example, the repository looks like it doesn't do much, and that is normal because there is only one layer below it, Room. In real practice you can have other data sources including network clients and cache. The repository's job is to abstract all data source logics.

    ViewModel:

    private MutableLiveData<Integer> studentLevel;  // This will store the student level
    private LiveData<List<Student>> studentsByLevel; // This will store the list of students
    
    public StudentViewModel(@NonNull Application application) {
        super(application);
        repository = new StudentRepository(application);
    
        studentLevel = new MutableLiveData<>();
    
        // Place your logic inside the ViewModel
        // Change in studentLevel will be reflected to studentsByLevel
        studentsByLevel = Transformations.switchMap(studentLevel, lvl -> {
            if (studentLevel == 0) {
                return repository.getAllStudents();
            } else {
                repository.getStudentsByLevel(stLevel);
            }
        });
    
        studentLevel.setValue(0) // Set initial student level.
    }
    
    .
    .
    .
    public void setStudentLevel(int level) { // Change studentLevel anytime.
        return studentLevel.setValue(level); 
    }
    
    public LiveData<List<Student>> getStudentList() {
        return studentsByLevel;
    }
    

    I am not a fan of LiveData, but here's what I would do. Keep all of your logic in ViewModel and make the view layer as simple as possible.

    Lastly, Activity:

    private StudentViewModel studentViewModel
    
    protected void onCreate(Bundle savedInstanceState) {
    
        ...
    
        RecyclerView recyclerView = findViewById(R.id.recycler_view);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        recyclerView.setHasFixedSize(true);
    
        final StudentAdapter adapter = new StudentAdapter();
        recyclerView.setAdapter(adapter);
    
        studentViewModel = ViewModelProviders.of(this).get(StudentViewModel.class);
        studentViewModel.observe(this, students -> {
            adapter.submitList(students);
        });
    
        // studentViewModel.setValue(1) // call this function anywhere you like.
    }
    

    Above code will show all students because we set the default value to 0 in the viewmodel. Call studentViewModel.setValue(/*any integer*/) to switch the list to any level.