# 결과물 미리보기
TimePicker를 통해서 알람 시간을 설정할 수 있다. 알람 시간을 끝내면 등록된 알람이 메인화면에 뜨게되고, 알람 켜기를 누르면 알람이 작동한다. 해당시간이 되면 아래 와 같이 푸시알림이 온다. 단 엄청 정확한 시간에는 오지 않는다. 안드로이드는 자원 리소스의 낭비를 줄이기 위해서 AlarmManger의 경우 몇가지 제약을 걸어뒀는데 그 덕에 매초마다 확인하지 않는다. 실제로 테스트해본 결과 약 1분가량이 늦게 푸시알림이 왔다.
# 구현 순서
- [시간재설정] 버튼의 로직을 우선 구현했다.
- TimePickerDialog를 통하여 TimePicker를 여는 행위를 정해줬다.
- 시간을 등록하면 내가 정의한 TimeModel이 생성되고 SharedPreference로 저장된다.
- 현재 등록 돼 있던 pendingIntent를 cancel시킨다.
- [알람 끄기] 버튼을 구현한다
- 버튼에 tag에 모델을 저장시켜놨다가 가져온다, 가져오는 과정에서 as를 통해 확인 과정을 거친다
- 가져온 모델을 newmodel로 만든 후 isOn을 반전시켜준 후 정의한 viewRender함수를 실행시킨다
- 알람 등록과 알람끄기를 각각 정의한다
- BroadcastReceiver를 상속받은 AlarmReciver를 정의하여 onReceive일때 알림을 띄어주는 행동 정의
# 알게된 것
- as
- alarmManger
- pendingIntent
- tag
## as
이번에 강의를 들으면서 as 몇번 사용했다. 일단 추측으로는 as (오브젝트)로 사용하면 해당 객체로 바꾼다는 의미인거 같은데, 첫번째 코드에서는 it.tag가 TimeModel객체이면 TimeModel로 사용하고 아니면 return의 맥락으로 사용했다. 그래서 찾아 보았다.
// tag가 TimeModel이면 model변수에 저장 ( 객체의 형을 비교해봄)
val model = it.tag as? TimeModel ?: return@setOnClickListener
val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
as는 TypeCasting즉 형변화를 해주는 것이었다. as 뒤에 지정한 Type(형)으로 변환시킨다. 그러나 지정한 Type으로 형변화를 할 수없다면 ClassCastException에러가 발생한다. 이걸 방지하기 위해서 as? 키워드를 사용한다. 그러면 형변화를 할 수 없을 시 null을 반환시킨다. 이걸 이용해서 첫번째처럼 형검사를 하고, null이라면 ?: 뒷구문이 실행 돼 return되는 것이다.
- as -> 형변환, 실패시 ClassCastException에러
- as? -> 형변환, 실패시 null 반환
### 함께 찾아본 is
자바에서 instanceof()와 같다고 한다. 데이터 형이 같은지 확인한다.
val num : Int = 100
if ( num is Int) // True or False
### 함께 찾아본 Any
Any는 어떤 객체도 될 수 있다.
var a: Any = 100
a = 10F
a = "Hello, world!!"
## tag
onOffButton.tag = model
onOffButton.setOnClickListener { it ->
val model = it.tag as? TimeModel ?: return@setOnClickListener
}
tag는 뭐 특별한게 없었다. tag는 Object타입이라 어떤 객체든지 담을 수 있다. 그렇기 때문에 model태그를 tag에 부착해줬다가 떼서 사용하는 것 같다. 또는 문자열도 되고 다 되는거 같았다.
## alarmManger
val calendar = Calendar.getInstance().apply {
set(Calendar.HOUR_OF_DAY, newModel.hour)
set(Calendar.MINUTE, newModel.minute)
}
val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
val intent = Intent(this, AlarmReciver::class.java)
val pendingIntent = PendingIntent.getBroadcast(this,1000,intent,PendingIntent.FLAG_UPDATE_CURRENT)
alarmManager.setInexactRepeating(AlarmManager.RTC_WAKEUP,calendar.timeInMillis,AlarmManager.INTERVAL_DAY,pendingIntent)
Calendar를 통해서 내가 설정한 시간대를 가진 객체를 만들어 calendar변수에 저장시킨다. 이 변수에는 알람이 울린 시간정보가 들어있다.
alarmManager를 만들고 setInexactRepeating을 통해 해당시간에 알람이 울리게 하며, pendingIntent로 전달시킨다. Intent가 아닌 pendingIntent인 이유는 바로 Intent하지 않고 지정한 시간대에 Intent되기 때문이다. 공식문서랑 블로그를 뒤져봤는데 확 와닿지가 않긴하지만, 이렇게 이해하고 넘어갔다.
알람이 시작되면 리시버로 전달되는 pendingIntent가 내가 정의했던 onReceive가 실행되며 알람을 보여주는 것 같다.
manifest에 리시버로 내가 생성한 알람리시버를 설정해줘야한다.
## pendingIntent <👩💻공부 필요👩💻>
매우 잘 정리해주셨다. 특정 시점에 intent를 실행하는 것인데, 그 특정시간대에는 다른 앱에서 Intent가 실행되고 있을 수 있다. 예를 들어 알림은 내가 다른앱을 사용중에 눌러서 들어가게 되는데 이런 경우를 처리하기 위함인것 같다. 이건 내가 확실히 이해 못했으니 정확한 정보는 아니다
# 유용하게 사용 한 것
- DataClass를 정의 한 후 property의 get()을 재정의하여 사용한 것
data class TimeModel(
val hour: Int,
val minute : Int,
var isOn : Boolean = false
){
val getStringHourMinute : String
get(){
return "$hour:$minute"
}
val getOnOffString : String
get() {
return (if (isOn) "알람 끄기" else "알람 켜기")
}
val timeText : String
get(){
val h = "%02d".format(if(hour<12) hour else hour -12)
val m = "%02d".format(minute)
return "$h:$m"
}
val ampmText: String
get(){
return if(hour < 12) "AM" else "PM"
}
}
data class사용하면서 어떻게 써야하지라는 생각 많이 들었는데 이번에 써보면서 정말 정말 좋고 편리하다는 생각이 들었다!
# 느낀 점
이번에는 정말로 거의 내 손으로 코드를 짰는데, 아쉽게도 pendingIntent, alarmManager가 잘 이해가 안됐다. 그래서 아쉬웠다. notification띄우는것도 저번에 했던거라서 이해 잘 갔는데..
intent의 개념에 대해서 좀 깊게 공부해볼 필요가 있는거 같다.
context도 마찬가지이다.
with, run 이런것도 많이쓰는데 사실 큰 차이를 잘 모르겠다 ㅠ