일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- 아비투스
- 캐치마인드
- 목적 중심 리더십
- 면접
- 한단어의힘
- 한달독서
- 베드트레이
- 안드로이드
- 목적중심리더십
- 자취필수템
- 한달어스
- 소프시스 밤부 좌식 엑슬 테이블
- 함수형 프로그래밍
- 한달브런치북만들기
- T자형인재
- 브런치작가되기
- 지지않는다는말
- 베드테이블
- 좌식테이블
- 슬기로운 온라인 게임
- 끝말잇기
- 1일1커밋
- 커스텀린트
- 프래그먼트
- 북한살둘레길
- 테트리스
- 리얼하다
- 재택근무
- 소프시스
- 어떻게 나답게 살 것인가
- Today
- Total
정상에서 IT를 외치다
[Android, MVP, Room] MVP 기본 예제 - Room 활용 본문
안녕하세요. 블랙진입니다.
지난시간 Android MVP 기본예제에 대한 포스팅을 진행했습니다. 이번에는 기본 예제에 Room을 사용하여 최근 보았던 유저 목록을 저장해보겠습니다.
현재 MVP 를 활용한 토이프로젝트를 총 3단계에 걸쳐서 리뷰를 진행하고 있습니다.
1. MVP 기본 예제
2. Room 사용하기
3. Dagger 사용하기
<작업>
기존 작업물에서 MainActivity 하단에 Recent User 버튼이 생겼습니다. 클릭했던 유저의 데이터를 Room 데이터베이스에 저장하여 RecentActivity 화면에 보여줍니다. 또한 Clear All 버튼을 클릭하여 모든 데이터를 삭제할 수 있습니다.
1. Room 설정하기
Room을 설정할 수 있는 방법은 포스팅을 확인해주세요.
'
2. 엔티티를 만들어 줍니다.
데이터베이스에 저장할 데이터 형식으로 User 객체를 아래와 같이 수정해 줍니다.
//테이블 이를을 설정합니다.
//데이터베이스에 저장할 데이터의 형식을 정합니다.
@Entity(tableName = "userTable")
public class User implements Serializable {
// Entity 클래스의 field 가 object 인 경우 @Embeded 를 설정해 줍니다.
@Embedded
@SerializedName("gender") public String gender;
@Embedded
@SerializedName("name") public Name name;
@Embedded
@SerializedName("location") public Location location;
@Embedded
@SerializedName("login") public Login login;
@Embedded
@SerializedName("picture") public Picture picture;
// email 값을 Primary Key 롤 설정하였습니다.
// Primary Key 는 @NonNull 을 필수로 설정해 주어야 합니다.
@SerializedName("email")
@PrimaryKey
@NonNull
public String email;
@SerializedName("phone") public String phone;
@SerializedName("cell") public String cell;
public int likeCnt = 0;
public String getFullName() { return name.title + "." + name.first + " " + name.last; }
public String getLikeCnt() { return "Like : " + likeCnt; }
@Override
public String toString() {
return getFullName();
}
}
유저 객체는 서버와의 통신을 통해 랜덤유저 정보를 받아오기 위한 모델입니다. 유저 객체에 Entity 를 추가해 주고 테이블 이름을 설정해 줌으로써 Room의 데이터 베이스로도 사용할 수 있게 됩니다.
3. 데이터 접근 객체를 생성합니다.
// 데이터 접근 객체를 생성합니다.
@Dao
public interface UserDao {
// 데이터베이스에 저장소를 추가합니다.
// 이미 저장된 항목이 있을 경우 데이터를 덮어씁니다.
@Insert(onConflict = OnConflictStrategy.REPLACE)
void add(User user);
// 해당 데이터를 업데이트 합니다.
@Update
void update(User user);
//해당 데이터를 삭제합니다.
@Delete
void delete(User user);
// 저장되어 있는 저장소 목록을 반환합니다.
// Flowable 형태의 자료를 반환하므로, 데이터베이스가 변경되면 알림을 받아 새로운 자료를 가져옵니다.
// 따라서 항상 최신 자료를 유지합니다.
@Query("SELECT * FROM userTable")
Flowable<List<User>> getUser();
// repositories 테이블의 모든 데이터를 삭제합니다.
@Query("DELETE FROM userTable")
void clearAll();
}
4. 룸 데이터 베이스를 생성합니다.
// 데이터베이스에서 사용하는 엔티티와 버전을 지정합니다.
@Database(entities = {User.class}, version = 1)
public abstract class UserDatabase extends RoomDatabase {
// 데이터베이스와 연결할 데이터 접근 객체를 정의합니다.
public abstract UserDao getUserDao();
}
5. 데이터 베이스 객체를 받기위한 싱글톤 클래스를 생성합니다.
// 싱글톤 패턴을 사용하여 데이터베이스 객체를 제공합니다.
public class UserDatabaseProvider {
private volatile static UserDatabase INSTANCE = null;
private UserDatabaseProvider() {}
public static UserDatabase getInstance(Context context) {
if(INSTANCE == null) {
synchronized (UserDatabase.class) {
INSTANCE = Room.databaseBuilder(
context,
UserDatabase.class,
"black_jin.db")
.build();
}
}
return INSTANCE;
}
}
이렇게 1~5 의 순서를 통해 Room을 설정해 보았습니다.
6. Main
MainContract
// 유저 추가를 해줍니다.
void addUser(UserDao userDao, User user);
MainContrat 에서 Room 을 위한 비즈니스 로직을 위해 위 두가지를 추가해 줍니다.
MainActivity
@Override
public void onClick(User user) {
// UserDatabase 에 저장합니다.
presenter.addUser(
UserDatabaseProvider.getInstance(this).getUserDao(),
user);
// DetailActivity 로 이동합니다.
Intent intent = new Intent(this, DetailActivity.class);
intent.putExtra(DetailActivity.KEY_USER, user);
startActivity(intent);
}
아이템 클릭시 상세 화면으로 넘어가는 부분에 addUser 을 추가해 줍니다.
MainPresenter
@Override
public void addUser(UserDao userDao, User user) {
disposable.add(
Observable.just(user)
.subscribeOn(Schedulers.io())
.subscribe(
item -> {
Log.d("MyTag","item : " + item + " 저장");
userDao.add(item);
},
error -> {
Log.d("MyTag","onError");
},
() -> {
Log.d("MyTag","onCompleted");
}
)
);
}
데이터 추가는 백그라운드 쓰레드에서 실행되어야 하므로 RxJava 를 사용했습니다.
7. Detail
DetailContract
public interface DetailContract {
interface View extends BaseContract.View {
void setText(String text);
}
interface Presenter extends BaseContract.Presenter<View> {
@Override
void setView(DetailContract.View view);
@Override
void releaseView();
// UserDao 값을 추가 인자로 보내줍니다.
void clickEvent(UserDao userDao, User user);
}
}
Like 클릭시 Room에 데이터의 변화를 알려줘야 하기 때문에 UserDao 값을 추가 인자로 보내줍니다.
DetailPresenter
private CompositeDisposable disposable;
DetailPresenter() {
disposable = new CompositeDisposable();
}
RxJava 를 사용하기 위해 disposable 객체를 초기화 해줍니다.
@Override
public void releaseView() {
disposable.clear();
}
Presenter 가 해제 되었을 때 disposable 을 clear 해주어야 합니다.
@Override
public void clickEvent(UserDao userDao, User user) {
user.likeCnt++;
view.setText(user.getLikeCnt());
RxEvent.getInstance().sendEvent(user);
// 유저 정보를 동기화 해줍니다.
disposable.add(
Observable.just(user)
.subscribeOn(Schedulers.io())
.subscribe(
item -> {
Log.d("MyTag","item : " + item + " 업데이트");
userDao.update(item);
},
error -> {
Log.d("MyTag","onError");
},
() -> {
Log.d("MyTag","onCompleted");
}
)
);
}
Like 클릭시 룸 데이터가 수정되었음을 알려줍니다.
7. Recent
MainAcivity 에서 보았던 유저들을 Room 에 저장하여 RecentActivity 에서 보고 삭제 할 수 있습니다.
RecentContract
public interface RecentContract {
interface View extends BaseContract.View {
void showProgress();
void hideProgress();
// Room 으로 부터 받은 데이터를 어댑터를 통해 보여줍니다.
void setItems(ArrayList<User> items);
}
interface Presenter extends BaseContract.Presenter<View> {
@Override
void setView(View view);
@Override
void releaseView();
// Room 으로 부터 데이터를 받아 옵니다.
void loadData(UserDao userDao);
// Room 데이터베이스에서 user 데이터 1개를 삭제합니다.
void deleteData(UserDao userDao, User user);
// Room 데이터를 전부 삭제 합니다.
void clearAll(UserDao userDao);
}
}
RecentActivity
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_recent);
setTitle("RECENT USER");
// recycler view 초기화
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(adapter);
adapter.setClickListener(this);
// presenter 와 연결
presenter.setView(this);
presenter.loadData(UserDatabaseProvider.
getInstance(this).
getUserDao());
}
@Override
protected void onDestroy() {
super.onDestroy();
// presenter 와의 연결을 해제합니다.
presenter.releaseView();
}
@Override
// 단일 아이템 삭제
public void onClick(User user) {
presenter.deleteData(
UserDatabaseProvider.getInstance(this).getUserDao(),
user);
}
@Override
public void showProgress() {
progressBar.setVisibility(View.VISIBLE);
}
@Override
public void hideProgress() {
progressBar.setVisibility(View.GONE);
}
@Override
public void setItems(ArrayList<User> items) {
adapter.setItems(items);
}
@OnClick(R.id.btn_clear_all)
// 모든 아이템 삭제
void onClick() {
presenter.clearAll(
UserDatabaseProvider.getInstance(this).getUserDao());
}
비즈니스 로직은 모두 Presenter 에서 이뤄 지면 View 의 변화만 Activity 에서 처리 됩니다.
RecentPresenter
@Override
public void loadData(UserDao userDao) {
disposable.add(
userDao.getUser()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe(__ -> {
view.showProgress();
})
.subscribe(
users -> {
view.setItems((ArrayList<User>) users);
view.hideProgress();
},
error -> {
Log.e("MyTag", error.getMessage());
})
);
}
Room 으로부터 모든 데이터를 가져옵니다.
@Override
public void deleteData(UserDao userDao, User user) {
disposable.add(
Observable.just(user)
.subscribeOn(Schedulers.io())
.subscribe(
item -> {
Log.d("MyTag", "item : " + item + " 삭제");
userDao.delete(item);
}
)
);
}
아이템 클릭 시 해당 아이템을 삭제합니다.
@Override
public void clearAll(UserDao userDao) {
disposable.add(
Observable.just("clear ALl")
.subscribeOn(Schedulers.io())
.subscribe(
item -> {
Log.d("MyTag", item);
userDao.clearAll();
}
)
);
}
모든 데이터를 삭제합니다.
<심화>
Room 에서의 Flowable 은 아래와 같은 특징이 있습니다.
/**
* The Flowable returned by Room never complete.
* It allows you to receive update upon database modifications.
*
* 1. When there is no user in the database and the query returns no rows,
* the Flowable will not emit, neither onNext, nor onError.
* 2. When there is a user in the database, the Flowable will trigger onNext.
* 3. Every time the user data is updated, the Flowable object will emit automatically,
* allowing you to update the UI based on the latest data.
*
* https://stackoverflow.com/questions/46541385/rxjava2-flowable-not-firing-oncomplete
* https://medium.com/androiddevelopers/room-rxjava-acb0cd4f3757
*/
// 저장되어 있는 저장소 목록을 반환합니다.
// Flowable 형태의 자료를 반환하므로, 데이터베이스가 변경되면 알림을 받아 새로운 자료를 가져옵니다.
// 따라서 항상 최신 자료를 유지합니다.
@Query("SELECT * FROM userTable")
Flowable<List<User>> getUser();
MVP 를 패턴에서의 Room 사용법에 대해 포스팅을 해봤습니다. View 와 Model 그리고 Presenter 을 분리하여 보다 자기 역활에 충실 할 수 있도록 짤 수 있게 노력해 보았습니다. 이제 Dagger 을 사용해 위 코드의 객체를 외부에서 주입할 수 있도록 코드를 변경하는 법에 대해 다음 포스팅을 진행해보겠습니다.
위 예제 코드는 링크 에서 확인하실 수 있습니다.
1. MVP 기본 예제
2. Room 사용하기
3. Dagger 사용하기
'안드로이드' 카테고리의 다른 글
[Android, TargetVersion 28] TargetVersion 28 에 따른 조치 사항 (2) | 2019.03.06 |
---|---|
[Android, MVP, Dagger] MVP 기본 예제 - Dagger 사용하기 (0) | 2019.02.08 |
[Android, MVP] MVP 기본 예제 (0) | 2019.02.07 |
[Android, Canvas, PorterDiff] PorterDiff.Mode 사용해 모서리가 둥근 이미지 만들기 (2) | 2019.01.21 |
[Android, Canvas, Paint] 안드로이드 캔버스 기본 예제 (4) | 2019.01.21 |