How to implement MVVM architecture in Android?

What is MVVM?

MVVM stands for ModelViewView Model.

Why MVVM?

  • Makes the code more understandable with every class like adapters, model, View Model in a separate package.
  • Makes the code maintainable for the long run because the repository class will be handling all API and Room data.
  • Makes the code testable.
  • Making changes, adding new features is easy because everything will be organized in a separate package so, it will be easy to make changes.

Model: The model is responsible for managing the application data and implements the business logic. It responds to the request of the view and also responds to the controller’s instructions to manipulate the data.

View: It represents the UI of the application in which we define our components like button, Text View, etc. The view can be a fragment or activity. View role in this pattern is to observe a View Model observable to get data to update UI elements accordingly.

View Model:  It is used to communicate between view and model and also prepares observable(s) that can be observed by a View.

Live Data: Live Data is a data holder and it is used to observe the changes of a particular view and then update the view when it is active.

Repository: The Repository is a simple Java class that is used to communicate between database | web server and View Model basically, in the repository class we fetch data from the server or the database, and View Model requests for the data that will be used for the View.

This is how we implement in the Android Studio:

In this project, we will make a Note App in which users can add a note, update the note and delete the note with the help of Room and MVVM.

Step 1: Add dependencies in build.gradle file
def lifecycle_version = "2.3.0"
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version"
// LiveData
implementation "androidx.lifecycle:lifecycle-livedata:$lifecycle_version"

I am using MVVM with a Room so add room dependencies also

def room_version = "2.2.6"

//room
implementation "androidx.room:room-runtime:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version"

Step 2:  Create a model class

In model class I will have three entities title, description, priority, and id will be auto-generated.

@Entity(tableName = "note_table")
  public class Note {
    @PrimaryKey(autoGenerate = true)
    private int id;
    private String title;
    private String description;
    private int priority;
    public Note(String title, String description, int priority) {
        this.title = title;
        this.description = description;
        this.priority = priority;
    }
    public void setId(int id) {
        this.id = id;
    }
    public int getId() {
        return id;
    }
    public String getTitle() {
        return title;
    }
    public String getDescription() {
        return description;
    }
    public int getPriority() {
        return priority;
    }
  }
 

Step 3:  Create a Room Database Interface

In this class, we will create Database object. We have to turn this class into skeleton (which means we can’t create multiple instances of this database)

 


@Database(entities = {Note.class}, version = 1)
public abstract class NoteDatabase extends RoomDatabase {
    private static NoteDatabase instance; 
   

    public abstract NoteDao noteDao();

    public static synchronized NoteDatabase getInstance(Context context) {
        if (instance == null) {
            instance = Room.databaseBuilder(context.getApplicationContext(),
                    NoteDatabase.class, "note_database")
                    .fallbackToDestructiveMigration()
                    .addCallback(roomCallback).build();
        }
        return instance;
    }
}

Step 4:  Create a Room Dao class

In this class, we write all our queries for CRUD operation. Live Data will use to observe the object.

 @Dao
    public interface NoteDao {

        @Insert
        void insert(Note note);
        @Update
        void update(Note note);
        @Delete
        void delete(Note note);

        @Query("DELETE FROM note_table")
        void deleteAllNotes();

        @Query("SELECT * FROM note_table ORDER BY priority DESC")
        LiveData<List> getAllNotes();
    }
 

Step 5:  Create a Repository class

In this class, we create a repository class object and we will access the repository methods.

 public class NoteRepository {

    private NoteDao noteDao;
    private LiveData<List> allNotes;

    public NoteRepository(Application application) {
        NoteDatabase database = NoteDatabase.getInstance(application);
        noteDao = database.noteDao();
        allNotes = noteDao.getAllNotes();
    }


    public void update(Note note) {
        new UpdateNoteAsyncTask(noteDao).execute(note);
    }

    public void delete(Note note) {
        new DeleteNoteAsyncTask(noteDao).execute(note);
    }

    public void deleteAllNotes() {
        new DeleteAllNotesAsyncTask(noteDao).execute();
    }

    public LiveData<List> getAllNotes() {
        return allNotes;
    }

    public void insert(Note note) {
        new InsertNoteAsyncTask(noteDao).execute(note);
    }

    private static class InsertNoteAsyncTask extends AsyncTask {
        private NoteDao noteDao;
        private InsertNoteAsyncTask(NoteDao noteDao) {
            this.noteDao = noteDao;
        }
        @Override
        protected Void doInBackground(Note... notes) {
            noteDao.insert(notes[0]);
            return null;
        }
    }

//it is static so its dosen't have to be reference to repository itself otherwise it will cause mem leak
    private static class UpdateNoteAsyncTask extends AsyncTask {
        private NoteDao noteDao;//to make data operation
        private UpdateNoteAsyncTask(NoteDao noteDao) {
            this.noteDao = noteDao;//since the class is static we cant access rep notedao so we are usinng constructor
        }
        @Override
        protected Void doInBackground(Note... notes) {
            noteDao.update(notes[0]);//not single node
            return null;
        }
    }

    private static class DeleteNoteAsyncTask extends AsyncTask {
        private NoteDao noteDao;
        private DeleteNoteAsyncTask(NoteDao noteDao) {
            this.noteDao = noteDao;
        }
        @Override
        protected Void doInBackground(Note... notes) {
            noteDao.delete(notes[0]);
            return null;
        }
    }
    private static class DeleteAllNotesAsyncTask extends AsyncTask {
        private NoteDao noteDao;
        private DeleteAllNotesAsyncTask(NoteDao noteDao) {
            this.noteDao = noteDao;
        }
        @Override
        protected Void doInBackground(Void... voids) {
            noteDao.deleteAllNotes();
            return null;
        }
    }

}

Step 6:  Create a View Model class

In this class, we create a repository class object and we will access the repository methods.

 public class NoteViewModel extends AndroidViewModel {

        private NoteRepository repository;
        private LiveData<List> allNotes;

        public NoteViewModel(@NonNull Application application) {
            super(application);

            repository = new NoteRepository(application);
            allNotes = repository.getAllNotes();
        }

        public void insert(Note note) {
            repository.insert(note);
        }
        public void update(Note note) {
            repository.update(note);
        }
        public void delete(Note note) {
            repository.delete(note);
        }
        public void deleteAllNotes() {
            repository.deleteAllNotes();
        }
        public LiveData<List> getAllNotes() {
            return allNotes;
        }

    }
 

Step 7:  Create an Activity(View) class      (Last Step)

In this class, we create a view model class object and we will access the methods of view model class.

 public class MainActivity extends AppCompatActivity {

    private NoteViewModel noteViewModel;//for communicating with ViewModel.
    public static final int ADD_NOTE_REQUEST = 1;
    public static final int EDIT_NOTE_REQUEST = 2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //initalize the view model
        //noteViewModel = new ViewModelProvider(this ).get(NoteViewModel.class);

        FloatingActionButton floatingActionButton=findViewById(R.id.add_note);
        floatingActionButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, AddEditNoteActivity.class);
                startActivityForResult(intent, ADD_NOTE_REQUEST);
            }
        });

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

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

        noteViewModel = new ViewModelProvider(this,
                ViewModelProvider.AndroidViewModelFactory.getInstance(this.getApplication())).get(NoteViewModel.class);


        //bcz this return live data
        //it will update only when app in foreground

        //this will be called when ever live data changes in our data
        //update recyclerview
        //when ever changes are made in the table our adapter will be updated with new list of notes


        noteViewModel.getAllNotes().observe(this, new Observer<List>() {
            @Override
            public void onChanged(List notes) {


                adapter.setNotes(notes);
            }
        });



        new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(0,
                ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) {
            @Override
            public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
                return false;
            }
            @Override
            public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
                noteViewModel.delete(adapter.getNoteAt(viewHolder.getAdapterPosition()));
                Toast.makeText(MainActivity.this, "Note deleted", Toast.LENGTH_SHORT).show();
            }
        }).attachToRecyclerView(recyclerView);


        adapter.setOnItemClickListener(new NoteAdapter.OnItemClickListener() {
            @Override
            public void onItemClick(Note note) {
                Intent intent = new Intent(MainActivity.this, AddEditNoteActivity.class);
                intent.putExtra(AddEditNoteActivity.EXTRA_ID, note.getId());
                intent.putExtra(AddEditNoteActivity.EXTRA_TITLE, note.getTitle());
                intent.putExtra(AddEditNoteActivity.EXTRA_DESCRIPTION, note.getDescription());
                intent.putExtra(AddEditNoteActivity.EXTRA_PRIORITY, note.getPriority());
                startActivityForResult(intent, EDIT_NOTE_REQUEST);
            }
        });

    }


    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        if (requestCode == ADD_NOTE_REQUEST && resultCode == RESULT_OK) {
            String title = data.getStringExtra(AddEditNoteActivity.EXTRA_TITLE);
            String description = data.getStringExtra(AddEditNoteActivity.EXTRA_DESCRIPTION);
            int priority = data.getIntExtra(AddEditNoteActivity.EXTRA_PRIORITY, 1);
            Note note = new Note(title, description, priority);
            noteViewModel.insert(note);
            Toast.makeText(this, "Note saved", Toast.LENGTH_SHORT).show();
        }

        else if (requestCode == EDIT_NOTE_REQUEST && resultCode == RESULT_OK) {
            int id = data.getIntExtra(AddEditNoteActivity.EXTRA_ID, -1);
            if (id == -1) {
                Toast.makeText(this, "Note can't be updated", Toast.LENGTH_SHORT).show();
                return;
            }
            String title = data.getStringExtra(AddEditNoteActivity.EXTRA_TITLE);
            String description = data.getStringExtra(AddEditNoteActivity.EXTRA_DESCRIPTION);
            int priority = data.getIntExtra(AddEditNoteActivity.EXTRA_PRIORITY, 1);
            Note note = new Note(title, description, priority);
            note.setId(id);//without this update will not happen
            noteViewModel.update(note);
            Toast.makeText(this, "Note updated", Toast.LENGTH_SHORT).show();
        }

        else {
            Toast.makeText(this, "Note not saved", Toast.LENGTH_SHORT).show();
        }
    }


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater menuInflater = getMenuInflater();
        menuInflater.inflate(R.menu.main_menu, menu);
        return true;
    }
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.delete_all_notes:
                noteViewModel.deleteAllNotes();
                Toast.makeText(this, "All notes deleted", Toast.LENGTH_SHORT).show();
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }
}

For complete project:

https://github.com/bilal96aslam/MVVM

You May Also Like

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.