@Dao interfaceScheduleDao{ @Query("select * from schedule") suspend fun selectScheduleList(): List<Schedule> @Query("select * from schedule where type = :type order by time desc") suspend fun selectScheduleListByOrder(type: Int): List<Schedule> @Query("select * from schedule where type = :type order by time asc") suspend fun selectScheduleListByOrderAsc(type: Int): List<Schedule> @Query("SELECT * FROM schedule WHERE time = :targetDate") suspend fun selectScheduleListOnDate(targetDate: Date): List<Schedule> @Insert(onConflict = OnConflictStrategy.IGNORE) suspend fun insertResult(data: Schedule): Long } @Dao interfaceScheduleDao{ @Query("select * from schedule") suspend fun selectScheduleList(): List<Schedule> @Query("select * from schedule where type = :type order by time desc") suspend fun selectScheduleListByOrder(type: Int): List<Schedule> @Query("select * from schedule where type = :type order by time asc") suspend fun selectScheduleListByOrderAsc(type: Int): List<Schedule> @Query("SELECT * FROM schedule WHERE time = :targetDate") suspend fun selectScheduleListOnDate(targetDate: Date): List<Schedule> @Insert(onConflict = OnConflictStrategy.IGNORE) suspend fun insertResult(data: Schedule): Long }
DBの設定
Roomデータベース(Database)の追加
@Database( //テーブルを追加する場合、entitiesにテーブルクラスを追加する entities =[ Schedule::class ], version = AppDatabase.DATABASE_VERSION ) @TypeConverters(DateConverters::class) abstract classAppDatabase():RoomDatabase(){ // DAO abstract fun ScheduleDao(): ScheduleDao //クラス内に作成されるシングルトンのことです。 //クラスのインスタンスを生成せずにクラスのメソッドにアクセスすることができ、 //データベースが1つしか存在しないようにできます。 companion object { const val DATABASE_VERSION: Int =1 @Volatile privatevarINSTANCE: AppDatabase?=null fun getDatabase(context: Context): AppDatabase { // if the INSTANCE is not null, then return it, // if it is, then create the database returnINSTANCE?:synchronized(this){ val instance = Room.databaseBuilder( context.applicationContext, AppDatabase::class.java, "calendar_app_database" ) // Wipes and rebuilds instead of migrating if no Migration object. // Migration is not part of this codelab. .fallbackToDestructiveMigration() .build() INSTANCE= instance // return instance instance } } } } @Database( //テーブルを追加する場合、entitiesにテーブルクラスを追加する entities =[ Schedule::class ], version = AppDatabase.DATABASE_VERSION ) @TypeConverters(DateConverters::class) abstract classAppDatabase():RoomDatabase(){ // DAO abstract fun ScheduleDao(): ScheduleDao //クラス内に作成されるシングルトンのことです。 //クラスのインスタンスを生成せずにクラスのメソッドにアクセスすることができ、 //データベースが1つしか存在しないようにできます。 companion object { const val DATABASE_VERSION: Int =1 @Volatile privatevarINSTANCE: AppDatabase?=null fun getDatabase(context: Context): AppDatabase { // if the INSTANCE is not null, then return it, // if it is, then create the database returnINSTANCE?:synchronized(this){ val instance = Room.databaseBuilder( context.applicationContext, AppDatabase::class.java, "calendar_app_database" ) // Wipes and rebuilds instead of migrating if no Migration object. // Migration is not part of this codelab. .fallbackToDestructiveMigration() .build() INSTANCE= instance // return instance instance } } } }
Repositoryの作成 - DI機能(Dependency Injection)
classScheduleRepositoryRoom( private val scheduleDao: ScheduleDao, ){ //メモ一覧を取得する suspend fun selectScheduleList(): List<Schedule>{ return scheduleDao.selectScheduleList() } //メモの種類を指定しメモ一覧を取得する suspend fun selectScheduleListByOrder(type: Int): List<Schedule>{ return scheduleDao.selectScheduleListByOrder(type) } //メモの種類を指定しメモ一覧を取得する suspend fun selectScheduleListByOrderAsc(type: Int): List<Schedule>{ return scheduleDao.selectScheduleListByOrderAsc(type) } //日付に紐づくメモ一覧を取得する suspend fun selectScheduleListOnDate(targetDate: Date): List<Schedule>{ return scheduleDao.selectScheduleListOnDate(targetDate) } //登録したメモを追加する suspend fun insertResult(data: Schedule): Long{ return scheduleDao.insertResult(data) } } classScheduleRepositoryRoom( private val scheduleDao: ScheduleDao, ){ //メモ一覧を取得する suspend fun selectScheduleList(): List<Schedule>{ return scheduleDao.selectScheduleList() } //メモの種類を指定しメモ一覧を取得する suspend fun selectScheduleListByOrder(type: Int): List<Schedule>{ return scheduleDao.selectScheduleListByOrder(type) } //メモの種類を指定しメモ一覧を取得する suspend fun selectScheduleListByOrderAsc(type: Int): List<Schedule>{ return scheduleDao.selectScheduleListByOrderAsc(type) } //日付に紐づくメモ一覧を取得する suspend fun selectScheduleListOnDate(targetDate: Date): List<Schedule>{ return scheduleDao.selectScheduleListOnDate(targetDate) } //登録したメモを追加する suspend fun insertResult(data: Schedule): Long{ return scheduleDao.insertResult(data) } }
ViewModelの作成
classDialogViewModel( private val repository: ScheduleRepositoryRoom, ):ViewModel(){ //メニュー登録のダイアログからのコールバック val state = MutableLiveData<DialogState<SimpleDialogFragment>>() //メニュー登録のダイアログからのコールバック private val _purchaseState = MutableLiveData<PurchaseState?>() val purchaseState: LiveData<PurchaseState?>get()= _purchaseState //一般的に、クラスのカプセル化を意識して、MutableLiveDataプロパティを非公開にし、LiveDataプロパティのみを公開します。 //StateFlow private val _scheduleList: MutableStateFlow<Resource<List<Schedule>>> =MutableStateFlow(Resource.Loading()) val scheduleList: StateFlow<Resource<List<Schedule>>> get()= _scheduleList.asStateFlow() /**
* DBにメモを追加する
*/ fun insertScheduleData(memo: String,time: String,type: Int){ _purchaseState.value = PurchaseState.PURCHASE_IN_PROGRESS viewModelScope.launch { try{ val schedule =Schedule().apply { this.id =0 this.memo = memo val replaceDate: String = time.replace("-","/") val formatter: DateFormat =SimpleDateFormat("yyyy/MM/dd", Locale.JAPANESE) val date: Date = formatter.parse(replaceDate)as Date this.time = date val formatter2: DateFormat =SimpleDateFormat("yyyy/MM", Locale.JAPANESE) val date2: Date = formatter2.parse(replaceDate)as Date this.time2 = date2 this.type = type } val count: Long = repository.insertResult(schedule) _purchaseState.postValue(PurchaseState.PURCHASE_SUCCESSFUL) }catch(e: Exception){ _purchaseState.value = PurchaseState.PURCHASE_ERROR } } } /**
* 日付(年月)に紐づくスケジュール一覧を取得する。
*/ fun selectScheduleListOnDate(targetDate: Date){ viewModelScope.launch { //データ取得 try{ //これが無いと、呼び出し側の lifecycle.repeatOnLifecycle が呼ばれない _scheduleList.value = Resource.Success(repository.selectScheduleListOnDate(targetDate)) }catch(e: Exception){ println("### e : "+e.message.toString()) _scheduleList.value = Resource.Error(e.message.toString()) } } } //////////////////////////////////////////////////////////// // inner class //////////////////////////////////////////////////////////// classDialogViewModelFactory( private val repository: ScheduleRepositoryRoom, ): ViewModelProvider.NewInstanceFactory(){ override fun <T: ViewModel>create(modelClass: Class<T>):T{ if(modelClass.isAssignableFrom(DialogViewModel::class.java)){ @Suppress("UNCHECKED_CAST") returnDialogViewModel(repository)asT } throwIllegalArgumentException("!!! Unknown DialogViewModel class !!!") } } } classDialogViewModel( private val repository: ScheduleRepositoryRoom, ):ViewModel(){ //メニュー登録のダイアログからのコールバック val state = MutableLiveData<DialogState<SimpleDialogFragment>>() //メニュー登録のダイアログからのコールバック private val _purchaseState = MutableLiveData<PurchaseState?>() val purchaseState: LiveData<PurchaseState?>get()= _purchaseState //一般的に、クラスのカプセル化を意識して、MutableLiveDataプロパティを非公開にし、LiveDataプロパティのみを公開します。 //StateFlow private val _scheduleList: MutableStateFlow<Resource<List<Schedule>>> =MutableStateFlow(Resource.Loading()) val scheduleList: StateFlow<Resource<List<Schedule>>> get()= _scheduleList.asStateFlow() /**
* DBにメモを追加する
*/ fun insertScheduleData(memo: String,time: String,type: Int){ _purchaseState.value = PurchaseState.PURCHASE_IN_PROGRESS viewModelScope.launch { try{ val schedule =Schedule().apply { this.id =0 this.memo = memo val replaceDate: String = time.replace("-","/") val formatter: DateFormat =SimpleDateFormat("yyyy/MM/dd", Locale.JAPANESE) val date: Date = formatter.parse(replaceDate)as Date this.time = date val formatter2: DateFormat =SimpleDateFormat("yyyy/MM", Locale.JAPANESE) val date2: Date = formatter2.parse(replaceDate)as Date this.time2 = date2 this.type = type } val count: Long = repository.insertResult(schedule) _purchaseState.postValue(PurchaseState.PURCHASE_SUCCESSFUL) }catch(e: Exception){ _purchaseState.value = PurchaseState.PURCHASE_ERROR } } } /**
* 日付(年月)に紐づくスケジュール一覧を取得する。
*/ fun selectScheduleListOnDate(targetDate: Date){ viewModelScope.launch { //データ取得 try{ //これが無いと、呼び出し側の lifecycle.repeatOnLifecycle が呼ばれない _scheduleList.value = Resource.Success(repository.selectScheduleListOnDate(targetDate)) }catch(e: Exception){ println("### e : "+e.message.toString()) _scheduleList.value = Resource.Error(e.message.toString()) } } } //////////////////////////////////////////////////////////// // inner class //////////////////////////////////////////////////////////// classDialogViewModelFactory( private val repository: ScheduleRepositoryRoom, ): ViewModelProvider.NewInstanceFactory(){ override fun <T: ViewModel>create(modelClass: Class<T>):T{ if(modelClass.isAssignableFrom(DialogViewModel::class.java)){ @Suppress("UNCHECKED_CAST") returnDialogViewModel(repository)asT } throwIllegalArgumentException("!!! Unknown DialogViewModel class !!!") } } }
Viewの作成 - 追加機能(一部抜粋)
classSimpleDialogFragment():DialogFragment(){ private lateinit varviewModel: DialogViewModel override fun onCreate(savedInstanceState: Bundle?){ super.onCreate(savedInstanceState) //ViewModelProviderの第一引数にrequireActivityを指定してあげると // Fragmentを使っているActivityでも同じViewModelのインスタンスをシェアできる。 viewModel =ViewModelProvider(requireActivity(), DialogViewModel.DialogViewModelFactory( (activity?.application as CalendarAppApplication).scheduleRepositoryRoom, )).get(DialogViewModel::class.java) } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View?{ val v = inflater.inflate(R.layout.fragment_simple_dialog, container,false) purchaseButton.setOnClickListener { //スケジュール登録を行う。 viewModel.insertScheduleData(memoEditText.text.toString(), date, type) observePurchaseState() } cancelButton.setOnClickListener { viewModel.state.value = DialogState.Cancel(this@SimpleDialogFragment) dismissAllowingStateLoss() } return v } override fun onCancel(dialog: DialogInterface){ super.onCancel(dialog) viewModel.state.value = DialogState.Cancel(this@SimpleDialogFragment) } /**
* ダイアログのコールバック処理
*/ private fun observePurchaseState(){ viewModel.purchaseState.observe(this){ purchaseState -> when(purchaseState){ PurchaseState.PURCHASE_IN_PROGRESS->{ purchaseButton.isEnabled =false showProgressBar() } PurchaseState.PURCHASE_SUCCESSFUL->{ hideProgressBar() // The purchase was successful! Show a message and dismiss the dialog. Toast.makeText(requireContext(),R.string.purchase_successful, Toast.LENGTH_SHORT).show() viewModel.state.value = DialogState.Ok(this@SimpleDialogFragment) dismissAllowingStateLoss() } PurchaseState.PURCHASE_ERROR->{ hideProgressBar() purchaseButton.isEnabled =true// enable so that the user can try again Toast.makeText(requireContext(),R.string.purchase_error, Toast.LENGTH_SHORT).show() } else->{ //nothing } } } } } classSimpleDialogFragment():DialogFragment(){ private lateinit varviewModel: DialogViewModel override fun onCreate(savedInstanceState: Bundle?){ super.onCreate(savedInstanceState) //ViewModelProviderの第一引数にrequireActivityを指定してあげると // Fragmentを使っているActivityでも同じViewModelのインスタンスをシェアできる。 viewModel =ViewModelProvider(requireActivity(), DialogViewModel.DialogViewModelFactory( (activity?.application as CalendarAppApplication).scheduleRepositoryRoom, )).get(DialogViewModel::class.java) } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View?{ val v = inflater.inflate(R.layout.fragment_simple_dialog, container,false) purchaseButton.setOnClickListener { //スケジュール登録を行う。 viewModel.insertScheduleData(memoEditText.text.toString(), date, type) observePurchaseState() } cancelButton.setOnClickListener { viewModel.state.value = DialogState.Cancel(this@SimpleDialogFragment) dismissAllowingStateLoss() } return v } override fun onCancel(dialog: DialogInterface){ super.onCancel(dialog) viewModel.state.value = DialogState.Cancel(this@SimpleDialogFragment) } /**
* ダイアログのコールバック処理
*/ private fun observePurchaseState(){ viewModel.purchaseState.observe(this){ purchaseState -> when(purchaseState){ PurchaseState.PURCHASE_IN_PROGRESS->{ purchaseButton.isEnabled =false showProgressBar() } PurchaseState.PURCHASE_SUCCESSFUL->{ hideProgressBar() // The purchase was successful! Show a message and dismiss the dialog. Toast.makeText(requireContext(),R.string.purchase_successful, Toast.LENGTH_SHORT).show() viewModel.state.value = DialogState.Ok(this@SimpleDialogFragment) dismissAllowingStateLoss() } PurchaseState.PURCHASE_ERROR->{ hideProgressBar() purchaseButton.isEnabled =true// enable so that the user can try again Toast.makeText(requireContext(),R.string.purchase_error, Toast.LENGTH_SHORT).show() } else->{ //nothing } } } } }
Viewの作成 - 表示機能(一部抜粋)
classCalendarFragment:Fragment(){ override fun onCreate(savedInstanceState: Bundle?){ super.onCreate(savedInstanceState) //ViewModelProviderの第一引数にrequireActivityを指定してあげると // Fragmentを使っているActivityでも同じViewModelのインスタンスをシェアできる。 dialogViewModel =ViewModelProvider(requireActivity(), DialogViewModel.DialogViewModelFactory( (activity?.application as CalendarAppApplication).scheduleRepositoryRoom, )).get(DialogViewModel::class.java) //ダイアログからのコールバック処理 dialogViewModel.state.observe(this){ when(it){ is DialogState.Ok ->{ //画面に表示するデータを取得する dialogViewModel.selectScheduleListOnDate2(date) } is DialogState.Cancel ->{ } else->{ } } }//dialogViewModel } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View?{ //監視 viewLifecycleOwner.lifecycleScope.launch { lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED){ dialogViewModel.scheduleList.collect(){ when(it){ is Resource.Loading ->{ } is Resource.Success ->{ //ここで取得したデータを画面に表示する } is Resource.Error ->{ } else->{ } } } } }//viewLifecycleOwner return binding.root } }classCalendarFragment:Fragment(){ override fun onCreate(savedInstanceState: Bundle?){ super.onCreate(savedInstanceState) //ViewModelProviderの第一引数にrequireActivityを指定してあげると // Fragmentを使っているActivityでも同じViewModelのインスタンスをシェアできる。 dialogViewModel =ViewModelProvider(requireActivity(), DialogViewModel.DialogViewModelFactory( (activity?.application as CalendarAppApplication).scheduleRepositoryRoom, )).get(DialogViewModel::class.java) //ダイアログからのコールバック処理 dialogViewModel.state.observe(this){ when(it){ is DialogState.Ok ->{ //画面に表示するデータを取得する dialogViewModel.selectScheduleListOnDate2(date) } is DialogState.Cancel ->{ } else->{ } } }//dialogViewModel } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View?{ //監視 viewLifecycleOwner.lifecycleScope.launch { lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED){ dialogViewModel.scheduleList.collect(){ when(it){ is Resource.Loading ->{ } is Resource.Success ->{ //ここで取得したデータを画面に表示する } is Resource.Error ->{ } else->{ } } } } }//viewLifecycleOwner return binding.root } }