-
Notifications
You must be signed in to change notification settings - Fork 6
Demo Week4
Android.Emulator.-.notto.2021-11-24.18-27-16.mp4
다행히 500 커밋을 넘지 않았습니다!
- 품사 분석기 라이브러리를 사용해 추출한 키워드를 Firebase Realtime Database에 저장
- 전체 알림을 ON/OFF 하여 매일 23:50에 리마인드 알림을 전송
- 오픈소스 라이선스와 개발자 소개
- 오늘 등록된 알람을 시간에 맞춰 알람 실행 (ex. 10시~12시까지 5분마다 알림)
- Not Todo의 푸시에서 성공/실패 여부를 체크할 수 있게 구현
- 11시 경에 "오늘 확인하지 못한 Not Todo를 확인해보라"는 푸시 알림을 띄워줌
- 선택한 캘린더 날짜에 해당하는 Not Todo 리스트 추출
- Not Todo의 달성여부를 갱신할때마다 캘린더의 날짜별 달성률을 변경
- db 디버깅을 위해 사용한 라이브러리
- dependencies에 추가하고 앱을 실행한 뒤 브라우저에서 Chrome://inspect에 접속하여 사용
- Chrome에서 이슈가 있기 때문에 Edge에서 사용하는 것을 추천
- 아니면 낮은 버전의 chrominum 사용하기
build.gradle에 의존성을 추가하고 OssLicenseMenuActivity
를 실행하면 라이브러리 목록을 확인할 수 있다.
startActivity(Intent(requireContext(), OssLicensesMenuActivity::class.java))
OssLicensesMenuActivity.setActivityTitle(getString(R.string.option_license_title))
옵션 화면 | 오픈소스 라이선스 목록 |
---|---|
- 키워드를 저장하고 카운팅하기 위해 사용
- DB에서 값을 가져오는 예시
private val database = Firebase
.database(firebaseDbUrl)
.getReference("keywords")
database.get().addOnSuccessListener {
Log.i(TAG, "Got value ${it.value}")
it.children.forEach { child ->
if (child.key != null && child.value != null) {
list.add(Keyword(child.key!!, (child.value!! as Long).toInt()))
}
}
}.addOnFailureListener{
Log.e(TAG, "Error getting data", it)
}.addOnCompleteListener {
list.sortByDescending { it.count }
orderedList.value = list
}.await()
동기적으로 실행하기 위해 kotlinx-coroutines-play-services
사용
- Github에서 CI 실행을 위해 Secrets에 키 저장
- 키워드 추출, Firebase DB 접근 관련 기능을 별도의 모듈
nottokeyword
로 분리 - 문장에서 키워드를 추출하기 위해
koalanlp-hnn
사용 - 사용 예시
private val firebaseDB = FirebaseDB(BuildConfig.FIREBASE_DB_URL)
viewModelScope.launch {
_items.value = firebaseDB.getKeywords()
}
망함간격 딱 맞고 엄청 예쁨✨
-
현재 캘린더까지의 뷰의 구조 : Activity -> Fragment -> Recyclerview -> CalendarAdapter(ConcatAdapter) -> ViewPager -> CalendarFragment
-
ViewPager2 : FragmentStateAdapter를 상속받아 구현
-
Fragment <-> Fragment 데이터 통신 : Adapter를 통해서 통신할 수 없기 때문에 FragmentManager를 활용해 데이터를 Key-Value 형태로 주고 받아야함
- setFragmentResult : 데이터를 전송할때 사용
- setFragmentResultListener : 데이터를 수신할때 사용
- Fragment 데이터 통신 관련 공식문서
- 같은 fragmentManager를 갖고 있어야 데이터 송수신이 가능하기 때문에 parentFragmentManager를 viewpager에 전달
AlarmManager, Broadcast Receiver, WorkManager
-
SharedPreference, AlarmManager, Broadcast Receiver 사용
-
하루 마무리 알림을 받을지 여부를 SharedPreference에 저장
-
하루 마무리 알림이 on인 경우
setAlarm
을 호출해주면 AlarmManager를 통해 하루에 한번, 23시 50분에 푸시 알림 -
AndroidManifest.xml
<receiver android:name=".ui.option.DayNotificationReceiver" android:exported="true" />
-
BroadcastReceiver
class DayNotificationReceiver : BroadcastReceiver() { private var notificationManager: NotificationManager? = null override fun onReceive(context: Context, intent: Intent) { notificationManager = context.getSystemService() DayNotificationManager.createNotificationChannel(context) DayNotificationManager.showNotification(context) }
-
setAlarm 메소드
object DayNotificationManager { fun setAlarm(context: Context) { val alarmManager: AlarmManager = context.getSystemService() ?: return val intent = Intent(context, DayNotificationReceiver::class.java) val pendingIntent = PendingIntent.getBroadcast( context, DayNotificationReceiver.NOTIFICATION_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT ) alarmManager.setInexactRepeating( AlarmManager.RTC_WAKEUP, getSelectedTime(), AlarmManager.INTERVAL_DAY, pendingIntent ) } }
- 정확한 시간을 위해 Alarm Manager + BroadcastReceiver 사용
- Work Manager가 상황에 따라 Alarm Manager로 알림을 설정해준다고 하는데, 그 설정하는 지연 자체가 길어서 그런지 비교적 정확한 시간에 들어와야하는 이 알림에는 조금 맞지 않음
- Alarm Manager가 지정된 시간 범위까지 setRepeating으로 돌고, 시간 주기마다 BroadcastReceiver에 알림을 보내고, BroadcastReceiver는 Intent로 받아온 Todo 데이터 정보를 보고 데이터에 맞게 Notification을 발생시킴.
- 다음 날의 Not Todo를 모아서 알림을 설정해줌
- Alarm Manager의 setRepeating만으론 구현에 한계가 있어서
Work Manager
도입- SetRepeating은 5분, 1시간 등 특정 간격만 반복할 수 있는데, 10시~12시까지 5분 간격으로 반복하다가 다음날 10시에 다시 시작하려면? -> 로직이 복잡해짐
- 1시간 단위로 실행되면서 현재 시간이 11시 이후인지 체크
- -> 밤 11시 경이라면 Work Manager로 다음 날의 Not Todo의 알림을 모아서 알림 설정을 해줌
- 다음 날 추가되는 알림들은 따로 설정을 해줌
- Reboot 등의 문제가 발생할 경우에도 Work Manager가 오늘의 Not Todo를 다 긁어서 재설정하면 됨.
val dailyWorkRequest =
PeriodicWorkRequestBuilder<AddDailyTodoWorker>(1, TimeUnit.HOURS)
.addTag(UPDATE_TOMORROW_DAILY_TODO)
.build()
val workManager = WorkManager.getInstance(this)
workManager.enqueueUniquePeriodicWork(
UPDATE_TOMORROW_DAILY_TODO,
ExistingPeriodicWorkPolicy.KEEP,
dailyWorkRequest
)
const val UPDATE_TOMORROW_DAILY_TODO = "com.notto.util.AddDailyTodoWorker"
@HiltWorker
class AddDailyTodoWorker @AssistedInject constructor(
@Assisted context: Context,
@Assisted params: WorkerParameters,
private val repository: TodoLabelRepository
) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
return try {
addTomorrowDailyTodosAlarm()
Result.success()
} catch (e: Exception) {
Result.failure()
}
}
private suspend fun addTomorrowDailyTodosAlarm() {
val calendar = Calendar.getInstance()
if (calendar.get(Calendar.HOUR_OF_DAY) >= 23) {
calendar.add(Calendar.DAY_OF_YEAR, 1)
repository.getTodosWithTodayDailyTodos(calendar.time.getDateString()).forEach {
val todo = it.todo
val dailyTodo = it.todayDailyTodo
if (todo.hasAlarm && dailyTodo.todoState != TodoState.SUCCESS) {
repository.addAlarm(todo)
}
}
}
}
}
-
디바이스가 꺼지면 설정한 Alarm Manager가 모두 삭제됨.
-
Reboot 후 Daily 회고 알림과 사라진 모든 Not Todo 알림 재설정
- Intent의 Boot_COMPLETED action이 발생할 때 로직 수행
- 회고 알림
- 별 다른 로직 없이 onReceive에서 수행
- 모든 알림 재설정
- 처음에는 BroadcastReceiver의 onReceive에서 CoroutineScope를 받아서 Room에 접근했는데, Reboot 되는 동안 다른 프로세스에 우선순위가 밀려서 그런지 Coroutine이 중단됨. 결국 Work Manager의 Coroutine Worker를 사용하게 됨.
-
AndroidManifest.xml
<receiver
android:name=".BootReceiver"
android:enabled="true"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>