Links & Dependenciesmain_menu.xmlactivity_add_note.xmladd_note_menu.xmlAddEditNoteActivity.javaAndroidManifest.xmlnote_item.xmlactivity_main.xmlNoteAdapter.javaNoteViewModel.javaMainActivity.javaNoteRepository.javaNoteDao.javaNoteDatabase.javaNote.java
“Adding Components” page with dependencies & instructions:
developer.android.com/topic/libraries/architecture/adding-components
CardView & Design Support Library:
developer.android.com/topic/libraries/support-library/packages#v7-cardview
developer.android.com/topic/libraries/support-library/packages#design
<?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <item android:id="@+id/delete_all_notes" android:title="Delete all notes" app:showAsAction="never" /> </menu>
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="16dp" tools:context=".AddEditNoteActivity"> <EditText android:id="@+id/edit_text_title" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="Title" android:inputType="text" /> <EditText android:id="@+id/edit_text_description" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="Description" android:inputType="textMultiLine" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:text="Priority:" android:textAppearance="@android:style/TextAppearance.Medium" /> <NumberPicker android:id="@+id/number_picker_priority" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout>
<?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <item android:id="@+id/save_note" android:icon="@drawable/ic_save" android:title="Save" app:showAsAction="ifRoom" /> </menu>
package com.codinginflow.architectureexample; import android.content.Intent; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.widget.EditText; import android.widget.NumberPicker; import android.widget.Toast; public class AddEditNoteActivity extends AppCompatActivity { public static final String EXTRA_ID = "com.codinginflow.architectureexample.EXTRA_ID"; public static final String EXTRA_TITLE = "com.codinginflow.architectureexample.EXTRA_TITLE"; public static final String EXTRA_DESCRIPTION = "com.codinginflow.architectureexample.EXTRA_DESCRIPTION"; public static final String EXTRA_PRIORITY = "com.codinginflow.architectureexample.EXTRA_PRIORITY"; private EditText editTextTitle; private EditText editTextDescription; private NumberPicker numberPickerPriority; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_add_note); editTextTitle = findViewById(R.id.edit_text_title); editTextDescription = findViewById(R.id.edit_text_description); numberPickerPriority = findViewById(R.id.number_picker_priority); numberPickerPriority.setMinValue(1); numberPickerPriority.setMaxValue(10); getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_close); Intent intent = getIntent(); if (intent.hasExtra(EXTRA_ID)) { setTitle("Edit Note"); editTextTitle.setText(intent.getStringExtra(EXTRA_TITLE)); editTextDescription.setText(intent.getStringExtra(EXTRA_DESCRIPTION)); numberPickerPriority.setValue(intent.getIntExtra(EXTRA_PRIORITY, 1)); } else { setTitle("Add Note"); } } private void saveNote() { String title = editTextTitle.getText().toString(); String description = editTextDescription.getText().toString(); int priority = numberPickerPriority.getValue(); if (title.trim().isEmpty() || description.trim().isEmpty()) { Toast.makeText(this, "Please insert a title and description", Toast.LENGTH_SHORT).show(); return; } Intent data = new Intent(); data.putExtra(EXTRA_TITLE, title); data.putExtra(EXTRA_DESCRIPTION, description); data.putExtra(EXTRA_PRIORITY, priority); int id = getIntent().getIntExtra(EXTRA_ID, -1); if (id != -1) { data.putExtra(EXTRA_ID, id); } setResult(RESULT_OK, data); finish(); } @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater menuInflater = getMenuInflater(); menuInflater.inflate(R.menu.add_note_menu, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.save_note: saveNote(); return true; default: return super.onOptionsItemSelected(item); } } }
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.codinginflow.architectureexample"> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity" android:launchMode="singleTop"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".AddEditNoteActivity" android:parentActivityName=".MainActivity" /> </application> </manifest>
<?xml version="1.0" encoding="utf-8"?> <android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginEnd="8dp" android:layout_marginStart="8dp" android:layout_marginTop="8dp"> <RelativeLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="8dp"> <TextView android:id="@+id/text_view_priority" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentEnd="true" android:text="1" android:textAppearance="@style/TextAppearance.AppCompat.Large" /> <TextView android:id="@+id/text_view_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentStart="true" android:layout_toStartOf="@id/text_view_priority" android:ellipsize="end" android:maxLines="1" android:text="Title" android:textAppearance="@style/TextAppearance.AppCompat.Large" /> <TextView android:id="@+id/text_view_description" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/text_view_title" android:text="Description" /> </RelativeLayout> </android.support.v7.widget.CardView>
<?xml version="1.0" encoding="utf-8"?> <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <android.support.v7.widget.RecyclerView android:id="@+id/recycler_view" android:layout_width="match_parent" android:layout_height="match_parent" tools:listitem="@layout/note_item" /> <android.support.design.widget.FloatingActionButton android:id="@+id/button_add_note" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|end" android:layout_margin="32dp" android:src="@drawable/ic_add" /> </android.support.design.widget.CoordinatorLayout>
package com.codinginflow.architectureexample; import android.support.annotation.NonNull; import android.support.v7.recyclerview.extensions.ListAdapter; import android.support.v7.util.DiffUtil; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; public class NoteAdapter extends ListAdapter<Note, NoteAdapter.NoteHolder> { private OnItemClickListener listener; public NoteAdapter() { super(DIFF_CALLBACK); } private static final DiffUtil.ItemCallback<Note> DIFF_CALLBACK = new DiffUtil.ItemCallback<Note>() { @Override public boolean areItemsTheSame(Note oldItem, Note newItem) { return oldItem.getId() == newItem.getId(); } @Override public boolean areContentsTheSame(Note oldItem, Note newItem) { return oldItem.getTitle().equals(newItem.getTitle()) && oldItem.getDescription().equals(newItem.getDescription()) && oldItem.getPriority() == newItem.getPriority(); } }; @NonNull @Override public NoteHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { View itemView = LayoutInflater.from(parent.getContext()) .inflate(R.layout.note_item, parent, false); return new NoteHolder(itemView); } @Override public void onBindViewHolder(@NonNull NoteHolder holder, int position) { Note currentNote = getItem(position); holder.textViewTitle.setText(currentNote.getTitle()); holder.textViewDescription.setText(currentNote.getDescription()); holder.textViewPriority.setText(String.valueOf(currentNote.getPriority())); } public Note getNoteAt(int position) { return getItem(position); } class NoteHolder extends RecyclerView.ViewHolder { private TextView textViewTitle; private TextView textViewDescription; private TextView textViewPriority; public NoteHolder(View itemView) { super(itemView); textViewTitle = itemView.findViewById(R.id.text_view_title); textViewDescription = itemView.findViewById(R.id.text_view_description); textViewPriority = itemView.findViewById(R.id.text_view_priority); itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { int position = getAdapterPosition(); if (listener != null && position != RecyclerView.NO_POSITION) { listener.onItemClick(getItem(position)); } } }); } } public interface OnItemClickListener { void onItemClick(Note note); } public void setOnItemClickListener(OnItemClickListener listener) { this.listener = listener; } }
package com.codinginflow.architectureexample; import android.app.Application; import android.arch.lifecycle.AndroidViewModel; import android.arch.lifecycle.LiveData; import android.support.annotation.NonNull; import java.util.List; public class NoteViewModel extends AndroidViewModel { private NoteRepository repository; private LiveData<List<Note>> 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<Note>> getAllNotes() { return allNotes; } }
package com.codinginflow.architectureexample; import android.arch.lifecycle.Observer; import android.arch.lifecycle.ViewModelProviders; import android.content.Intent; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.design.widget.FloatingActionButton; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.helper.ItemTouchHelper; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.widget.Toast; import java.util.List; public class MainActivity extends AppCompatActivity { public static final int ADD_NOTE_REQUEST = 1; public static final int EDIT_NOTE_REQUEST = 2; private NoteViewModel noteViewModel; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); FloatingActionButton buttonAddNote = findViewById(R.id.button_add_note); buttonAddNote.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 = ViewModelProviders.of(this).get(NoteViewModel.class); noteViewModel.getAllNotes().observe(this, new Observer<List<Note>>() { @Override public void onChanged(@Nullable List<Note> notes) { adapter.submitList(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); 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); } } }
package com.codinginflow.architectureexample; import android.app.Application; import android.arch.lifecycle.LiveData; import android.os.AsyncTask; import java.util.List; public class NoteRepository { private NoteDao noteDao; private LiveData<List<Note>> allNotes; public NoteRepository(Application application) { NoteDatabase database = NoteDatabase.getInstance(application); noteDao = database.noteDao(); allNotes = noteDao.getAllNotes(); } public void insert(Note note) { new InsertNoteAsyncTask(noteDao).execute(note); } 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<Note>> getAllNotes() { return allNotes; } private static class InsertNoteAsyncTask extends AsyncTask<Note, Void, Void> { private NoteDao noteDao; private InsertNoteAsyncTask(NoteDao noteDao) { this.noteDao = noteDao; } @Override protected Void doInBackground(Note... notes) { noteDao.insert(notes[0]); return null; } } private static class UpdateNoteAsyncTask extends AsyncTask<Note, Void, Void> { private NoteDao noteDao; private UpdateNoteAsyncTask(NoteDao noteDao) { this.noteDao = noteDao; } @Override protected Void doInBackground(Note... notes) { noteDao.update(notes[0]); return null; } } private static class DeleteNoteAsyncTask extends AsyncTask<Note, Void, Void> { 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<Void, Void, Void> { private NoteDao noteDao; private DeleteAllNotesAsyncTask(NoteDao noteDao) { this.noteDao = noteDao; } @Override protected Void doInBackground(Void... voids) { noteDao.deleteAllNotes(); return null; } } }
package com.codinginflow.architectureexample; import android.arch.lifecycle.LiveData; import android.arch.persistence.room.Dao; import android.arch.persistence.room.Delete; import android.arch.persistence.room.Insert; import android.arch.persistence.room.Query; import android.arch.persistence.room.Update; import java.util.List; @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<Note>> getAllNotes(); }
package com.codinginflow.architectureexample; import android.arch.persistence.db.SupportSQLiteDatabase; import android.arch.persistence.room.Database; import android.arch.persistence.room.Room; import android.arch.persistence.room.RoomDatabase; import android.content.Context; import android.os.AsyncTask; import android.support.annotation.NonNull; @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; } private static RoomDatabase.Callback roomCallback = new RoomDatabase.Callback() { @Override public void onCreate(@NonNull SupportSQLiteDatabase db) { super.onCreate(db); new PopulateDbAsyncTask(instance).execute(); } }; private static class PopulateDbAsyncTask extends AsyncTask<Void, Void, Void> { private NoteDao noteDao; private PopulateDbAsyncTask(NoteDatabase db) { noteDao = db.noteDao(); } @Override protected Void doInBackground(Void... voids) { noteDao.insert(new Note("Title 1", "Description 1", 1)); noteDao.insert(new Note("Title 2", "Description 2", 2)); noteDao.insert(new Note("Title 3", "Description 3", 3)); return null; } } }
package com.codinginflow.architectureexample; import android.arch.persistence.room.Entity; import android.arch.persistence.room.PrimaryKey; @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; } }