경험의 기록

2021.05.22 - [안드로이드/개발] - [Android 개발일지] MVVM 패턴으로 Todo, Done List 만들기 - (1) 기획, Mockup

 

[Android 개발일지] MVVM 패턴으로 Todo, Done List 만들기 - (1) 기획, Mockup

MVVM 패턴에 대해 공부하고, 이를 좀 더 활용해보고자 TodoList 프로젝트를 기획하게 되었다. 천리길도 한걸음부터라는 마음으로, 프로젝트 계의 클래식이라고 할 수 있는 TodoList 를 MVVM 패턴을 사용

hanyeop.tistory.com

에서 이어지는 글입니다.

이제 Mockup한 대로 전체적인 레이아웃을 만들어보려고 한다.

 


1️⃣ 레이아웃 만들기

종속성, 뷰바인딩 추가

android {
    buildFeatures {
        viewBinding true
    }
}

dependencies {
    // 뷰페이저 2
    implementation "androidx.viewpager2:viewpager2:1.0.0"
}

기본적으로 익스텐션 대신 뷰바인딩을 사용할 것이고,

뷰페이저를 사용하기 위하여 종속성을 추가해준다.

 

 

탭레이아웃 + 뷰페이저 추가

2021.05.12 - [안드로이드/기본] - [Android] ViewPager2와 TabLayout 사용하여 레이아웃 만들기

 

[Android] ViewPager2와 TabLayout 사용하여 레이아웃 만들기

탭과 뷰페이저를 사용하면 스와이프해서 다른 프래그먼트를 볼 수 있는 탭을 만들 수 있다. 사용해보기 종속성 추가 android { buildFeatures { viewBinding true } } dependencies { // 뷰페이저 2 implementati..

hanyeop.tistory.com

기본적으로 뷰페이저와 탭레이아웃을 사용하여 스와이프 되는 레이아웃을 구현하려고 하므로

위 링크에서 서술한 방식으로 레이아웃을 만들어준다.

 

검색창 만들기

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:tools="http://schemas.android.com/tools"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <item android:id="@+id/menu_search"
        android:title="Search"
        android:icon="@drawable/ic_baseline_search_24"
        android:iconTint="@color/white"
        app:showAsAction="ifRoom"
        tools:targetApi="o"
        app:actionViewClass="androidx.appcompat.widget.SearchView" />

</menu>

검색창으로 사용할 메뉴를 만들어주고

 

class TodoListFragment : Fragment() {

    private var binding : FragmentTodoListBinding? = null

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // 상단 메뉴 추가
        setHasOptionsMenu(true)
        // 뷰바인딩
        binding = FragmentTodoListBinding.inflate(inflater,container,false)
        return binding!!.root
    }

    // 서치바 추가
    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
        inflater.inflate(R.menu.main_menu,menu)

        val search = menu?.findItem(R.id.menu_search)
        val searchView = search?.actionView as? SearchView
    }

    // 프래그먼트는 뷰보다 오래 지속 . 프래그먼트의 onDestroyView() 메서드에서 결합 클래스 인스턴스 참조를 정리
    override fun onDestroyView() {
        binding = null
        super.onDestroyView()
    }
}

TodoListFragment에서 뷰바인딩 후,

onCreateOptionMenu를 오버라이드 하고, setHasOptionsMenu(true) 를 추가하여 서치바를 추가해준다.

 

 

첫번째 탭에 검색창이 잘 구현된 것을 확인할 수 있고

 

스와이프나 탭 클릭 시 다른 탭으로 잘 이동된다.

 

 

2️⃣ DB 만들기

종속성 추가

plugins {
    id 'kotlin-kapt'
}


dependencies {
    def room_version = "2.3.0"
    // room
    implementation "androidx.room:room-runtime:$room_version"
    kapt "androidx.room:room-compiler:$room_version"
    // room 코루틴
    implementation "androidx.room:room-ktx:$room_version"
    
    def lifecycle_version = "2.3.0"
    // ViewModel - 라이프 사이클
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
    // LiveData - 데이터의 변경 사항을 알 수 있음
    implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"

    // 뷰모델 생성하기 쉽게 해줌
    implementation 'androidx.fragment:fragment-ktx:1.1.0'
}

Room, 뷰모델, 라이브데이터를 사용하기위해 추가해준다.

여기선 Room에 대해 간략하게만 서술하며

 

2021.05.12 - [안드로이드/AAC, MVVM] - [Android] Room 활용하여 데이터 저장하기

 

[Android] Room 활용하여 데이터 저장하기

안드로이드에서는 로컬 DB에 데이터를 저장하기 위해 SQLite를 지원한다. 허나 SQLite는 사용하기 복잡하여 어렵기 때문에 SQLite에 대한 추상화 레이어를 제공하는 Room 라이브러리를 지원한다. 2021.0

hanyeop.tistory.com

여기서 자세한 내용을 확인할 수 있다.

Entity

@Entity
data class Memo(
    val check : Boolean,
    val content : String
    ){
    @PrimaryKey(autoGenerate = true)
    var id = 0
}

메모 내용과 그 일을 했는지 체크할 수 있는 체크 변수를 만들어주고, id는 자동으로 할당한다.

 

Dao

@Dao
interface MemoDao {
    // OnConflictStrategy.IGNORE = 동일한 아이디가 있을 시 무시
    @Insert(onConflict = OnConflictStrategy.IGNORE)
    suspend fun addUser(memo : Memo)

    @Update
    suspend fun updateUser(memo : Memo)

    @Delete
    suspend fun deleteUser(memo : Memo)

    @Query("SELECT * FROM Memo ORDER BY id ASC")
    fun readAllData() : Flow<List<Memo>>

    @Query("SELECT * FROM Memo WHERE content LIKE :searchQuery")
    fun searchDatabase(searchQuery : String) : Flow<List<Memo>>
}

TodoList를 만들기 위한 쿼리들을 만들어준다.

 

Room Database

/* entities = 사용할 엔티티 선언, version = 엔티티 구조 변경 시 구분해주는 역할
   exportSchema = 스키마 내보내기 설정 */
@Database(entities = [Memo::class], version = 1, exportSchema = false)
abstract class MemoDatabase : RoomDatabase(){

    abstract fun memoDao() : MemoDao

    companion object{
        /* @Volatile = 접근가능한 변수의 값을 cache를 통해 사용하지 않고
        thread가 직접 main memory에 접근 하게하여 동기화. */
        @Volatile
        private var instance : MemoDatabase? = null

        // 싱글톤으로 생성 (자주 생성 시 성능 손해). 이미 존재할 경우 생성하지 않고 바로 반환
        fun getDatabase(context : Context) : MemoDatabase? {
            if(instance == null){
                synchronized(MemoDatabase::class){
                    instance = Room.databaseBuilder(
                        context.applicationContext,
                        MemoDatabase::class.java,
                        "memo_database"
                    ).build()
                }
            }
            return instance
        }
    }
}

싱글톤으로 데이터베이스를 생성해준다.

 

Repository

// 앱에서 사용하는 데이터와 그 데이터 통신을 하는 역할
class MemoRepository(private val memoDao: MemoDao) {
    val readAllData : Flow<List<Memo>> = memoDao.readAllData()

    suspend fun addUser(memo: Memo){
        memoDao.addUser(memo)
    }

    suspend fun updateUser(memo: Memo){
        memoDao.updateUser(memo)
    }

    suspend fun deleteUser(memo: Memo){
        memoDao.deleteUser(memo)
    }

    fun searchDatabase(searchQuery: String): Flow<List<Memo>> {
        return memoDao.searchDatabase(searchQuery)
    }
}

Dao에서 정의한 메소드들을 사용하기 위해 저장소를 만들어준다.

뷰모델에서 호출되어 DB에 직접적으로 접근하여 통신한다.

 

ViewModel

// 뷰모델은 DB에 직접 접근하지 않아야함. Repository 에서 데이터 통신.
class MemoViewModel(application: Application) : AndroidViewModel(application) {

    val readAllData : LiveData<List<Memo>>
    private val repository : MemoRepository

    init{
        val memoDao = MemoDatabase.getDatabase(application)!!.memoDao()
        repository = MemoRepository(memoDao)
        readAllData = repository.readAllData.asLiveData()
    }

    fun addUser(memo : Memo){
        viewModelScope.launch(Dispatchers.IO) {
            repository.addUser(memo)
        }
    }

    fun updateUser(memo : Memo){
        viewModelScope.launch(Dispatchers.IO) {
            repository.updateUser(memo)
        }
    }

    fun deleteUser(memo : Memo){
        viewModelScope.launch(Dispatchers.IO) {
            repository.deleteUser(memo)
        }
    }

    fun searchDatabase(searchQuery: String): LiveData<List<Memo>> {
        return repository.searchDatabase(searchQuery).asLiveData()
    }
}

프래그먼트와 연결하여

뷰에 사용할 데이터를 가공해주는 Viewmodel를 생성한다.

데이터의 입력,수정,삭제 등은 viewmodelscope에서 비동기로 처리한다.

 

 

Fab 만들기

<com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/add_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="20dp"
        android:layout_marginBottom="20dp"
        android:clickable="true"
        android:focusable="true"
        android:src="@drawable/ic_baseline_edit_24"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />

첫번째 탭에 Fab를 만들어준다.

Fab 클릭 시 메모를 할 수 있는 창이 뜨도록 할 것이다.

 

프래그먼트와 뷰모델 연결

class TodoListFragment : Fragment() {

    private var binding : FragmentTodoListBinding? = null
    private val memoViewModel: MemoViewModel by viewModels()

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // 상단 메뉴 추가
        setHasOptionsMenu(true)
        // 뷰바인딩
        binding = FragmentTodoListBinding.inflate(inflater,container,false)

        binding!!.addButton.setOnClickListener {
            Toast.makeText(activity, "테스트", Toast.LENGTH_SHORT).show()
            onFabClicked()
        }

        return binding!!.root
    }
    
    // Fab 클릭시 다이얼로그 띄움
    fun onFabClicked(){
        val memo = Memo(false,"테스트")
        memoViewModel.addUser(memo)
    }

Fab가 클릭되었을 때 다이얼로그를 띄워야하지만, 일단 테스트를 위해 임의의 값을 저장해보았다.

프래그먼트에서 toast의 contextactivity를 사용하며

뷰모델은 프래그먼트 ktx를 사용했기 때문에

    private val memoViewModel: MemoViewModel by viewModels() 로 쉽게 초기화 할 수 있다.

 

2020.09.10 - [안드로이드/기본] - [Android] 안드로이드 스튜디오 SQLite Database 경로(위치)

 

[Android] 안드로이드 스튜디오 SQLite Database 경로(위치)

안드로이드 에뮬레이터를 켠 상태로 Device File Explorer를 누르면 나오는 화면에서 으로 들어가게 되면 DB정보가 있다. 이곳에 DB를 붙여넣거나 삭제할 수 있으며, DB의 정보를 확인하고 싶다면 Save

hanyeop.tistory.com

 

 

위 링크의 방법으로 DB를 확인해보면,

잘 생성되었음을 확인할 수 있다.

 

 

 

https://github.com/HanYeop/TodoneList

 

HanYeop/TodoneList

Todo-Done List . Contribute to HanYeop/TodoneList development by creating an account on GitHub.

github.com

 

 

 

 

 

 

반응형

공유하기

facebook twitter kakaoTalk kakaostory naver band
loading