Search code examples
androidandroid-sqliteandroid-roomandroid-livedataandroid-architecture-components

how to get the first or (any) element from a LiveData List in Android MVVM architecture?


I used MVVM architecture to build an Android application with a Repository as a mediation between the ViewModel and Room database. In one of the functions, I retrieve a list of objects from a table in the repository class so that I can call it from the ViewModel class.

how to retrieve the first element in the list from the repository class instead of observing the LiveData in any one of the activities?

Here is my repository class:

public class StudentRepository {

    private StudentDAO mStudentDAO;
    private LiveData<List<Student>> mAllStudents;
 
    public StudentRepository(Application application){
        StudentRoomDatabase db = StudentRoomDatabase.getDatabase(application);
        mStudentDAO= db.StudentDAO();
        mAllStudents= mStudentDAO.getAllStudents();
    }


   
    public LiveData<List<Word>> getAllStudents(){
 
        // What I need to return the first element in the mAllStudents list
        // in order to type the first student name in the Log.d 
       
 **Log.d("First_Name",mAllStudent.get(0));
// I want to type the student name in the Log without adding a function in
the viewModel and observe that in any Activity**

        return mAllStudents;
    }

}

Solution

  • how to get the first or (any) element from a LiveData List in Android MVVM architecture

    If you want to get a particular element from a LiveData list, then you need to limit your query with an offset.

    By default limiting a query doesn't consider an offset to it; so the default offset value is 0.

    For instance, in your Dao:

    The below queries of Example 1 & 2 are equivalent, because the default offset value is 0.

    Example

    @Query("SELECT * FROM students LIMIT  :limit")
    LiveData<List<Student>> getStudents(int limit);
    
    // Query
    getStudents(1); // returns the first row of the list
    

    Example 2

    @Query("SELECT * FROM students LIMIT  :limit OFFSET :offset")
    LiveData<List<Student>> getStudents(int limit, int offset);
    
    // Query
    getStudents(1, 0); // returns the first row of the list
    

    Note: Here I am assuming your model class is Student

    This is about the first row; but to return row number x then you need to manipulate the offset to be: x-1, as the offset is 0-based value

    Example 3 (Same Dao query as Example 2)

    getStudents(1, 1); // returns the second row
    getStudents(1, 4); // returns the fifth row
    

    If you want to return more than one row, then you need to manipulate the LIMIT value, so to return x rows from the results, then limit the query with x.

    getStudents(2, 1); // returns the second and third rows
    getStudents(3, 4); // returns the fifth, sixth, and seventh rows
    

    Hope this addresses your question

    Edit as per comments

    I already have a list returned by another query @Query("SELECT * FROM students) LiveData<List<Student>> getStudents(); So the returned value is a list. I want to get the first element from the list. This answer returns the first element truly, but I need to pass all the steps (to define this method in the ViewModel class and observe it in the MainActivity in order to get the list or any element in the list). What I need is to type the first value in the list while I am using the function in the repository class. –

    Now you are using below query

    @Query("SELECT * FROM students")
    LiveData<List<Student>> getStudents();
    

    And you want to:

    1. Get the first or any element in the list. and to do so according to what is mentioned above
    2. Pass all the steps (to define this method in the ViewModel class and observe it in the MainActivity in order to get the list or any element in the list).

    So to do that:

    In Dao:: Change your query to

    @Query("SELECT * FROM students LIMIT  :limit OFFSET :offset")
    LiveData<List<Student>> getAllStudents(int limit, int offset);
    

    In Repository:

    public class StudentRepository {
    
        ...
    
        public LiveData<List<Student>> getAllStudents(final int limit, final int offset) {
            return mDAO.getAllStudents(limit, offset);
        }
    }
    

    In ViewModel:

    public LiveData<List<Student>> getAllStudents(final int limit, final int offset) {
        return mRepository.getAllStudents(limit, offset);
    }
    

    In Activity:

    private void getAllStudents(int limit, int offset) {
        mViewModel.getAllStudents(limit, offset).observe(this, new Observer<List<Student>>() {
            @Override
            public void onChanged(List<Student> students) {
                if (students != null) {
                    // Here you can set RecyclerView list with `students` or do whatever you want
    
                }
            }
        });
    }
    

    And to test that:

    getAllStudents(1, 0); // return first row from the table.
    getAllStudents(2, 0); // return first 2 rows from the table.
    getAllStudents(2, 1); // return 2nd and 3rd rows from the table.
    getAllStudents(-1, 5); // remove first 5 rows from the table.
    

    And to return the first element in the mAllStudents list in order to type the first student name in the Log.d

    So, In your Activity

    mViewModel.getAllStudents(1, 0).observe(this, new Observer<List<Student>>() {
        @Override
        public void onChanged(List<Student> students) {
            if (students != null) {
                Student student = students.get(0);
                Log.d("First_Name", student.getName());
            }
        }
    });
    

    Edit is it possible to return any element of the list without following all the steps such as writing a function in the ViewModel and observe that in the MainActivity? My question is not to follow all the steps.

    Yes it's possible, but the above model is the recommended model by Google, You can return a List<Student> from Dao query instead of LiveData<List<Student>>, but the bad news are:

    1. You have to handle that in a separate background thread; because the LiveData do that for free.
    2. You will lose the value of the LiveData; so you have to manually refresh the list to check any update as you won't be able to use the observer pattern.

    So, you can omit using ViewModel and Repository, and do all the stuff from the activity as follows:

    private Executor mExecutor = Executors.newSingleThreadExecutor();
    
    public void getAllStudents(final int limit, final int offset) {
    
        final StudentDAO mDAO = StudentDatabase.getInstance(getApplicationContext()).getStudentDAO();
    
        mExecutor.execute(new Runnable() {
            @Override
            public void run() {
                List<Student> students = mDAO.getAllStudents(limit, offset);
                Student student = students.get(0);
                Log.d("First_Name", student.getName());
            }
        });
    }
    
    // Usage: getAllStudents(1, 0);
    

    And the query for the Dao:

    @Query("SELECT * FROM students LIMIT  :limit OFFSET :offset")
    List<Student> getAllStudents(int limit, int offset);