- AlarmManager
- Notification
- getSharedPreference
- Broadcast Receiver
- TimePicker๋ก ์๊ฐ ์ค์ ํ๊ธฐ
- ์๋ฆผ ON / OFF
๊ฐ๋ฐ ๊ณผ์ (๋ ธ์ ์์ ํ์ธํ๊ธฐ)
์ค๊ฐ์ ์๋ฆผ์ผ๋ก ์ค์ ํ ์๊ฐ์ ๋ณด์ฌ์ฃผ๊ณ ํ๋จ์ ๋ ๊ฐ์ ๋ฒํผ์ ๋ฐฐ์นํด์ ์๋ฆผ์ ์จ์คํํ๊ณ ์๊ฐ์ ์ฌ์ค์ ํ ์ ์๋๋ก ๊ตฌํํ๋ คํ๋ค.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<View
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginHorizontal="50dp"
android:background="@drawable/background_blackring"
app:layout_constraintBottom_toTopOf="@id/onOffButton"
app:layout_constraintDimensionRatio="H, 1:1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/timeTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="09:00"
android:textSize="50sp"
app:layout_constraintBottom_toTopOf="@+id/onOffButton"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed" />
<TextView
android:id="@+id/ampmTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="AM"
android:textSize="25sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/timeTextView" />
<Button
android:id="@+id/onOffButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="30sp"
android:text="@string/on_alarm"
app:layout_constraintBottom_toTopOf="@+id/changeAlarmButton"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<Button
android:id="@+id/changeAlarmButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="30sp"
android:layout_marginBottom="30sp"
android:text="@string/time_change"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
ํ ์คํธ๋ง ๋ณด์ฌ์ฃผ๊ธฐ์๋ ๋ฐ๋ฐํด์ ๋๊ทธ๋ ํํ์ ์์ญ ๋ด๋ถ์ ์๊ฐ์ ํ์ํ๋๋ก ํ๋ค.
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/white" />
<stroke
android:width="1dp"
android:color="@color/black" />
<size
android:width="250dp"
android:height="250dp" />
</shape>
drawable
๋ก shape
๋ฅผ ๊ตฌํํ๊ณ oval
ํ์
์ ์ค์ ํ๋ค. stroke
๋ฅผ ํตํด ์์ ์ ์ ์ถ๊ฐํด์คฌ๋ค.
๋ช ์๊ฐ ๋ค์ ์๋์ด ์ธ๋ฆด ๊ฒ์ธ์ง ์ธํ
ํ๋ ๊ฒ์ TimePicker
๋ฅผ ์ด์ฉํ๋ค.
private val changeTimeButton: Button by lazy {
findViewById(R.id.changeAlarmButton)
}
์ฐ์ ์๊ฐ์ ์ค์ ํ๋ button ๋ ์ด์์๊ณผ ์ฐ๊ฒฐํด์คฌ๋ค. ์ดํ TimePicker
๋กค ์ค์ ํด์คฌ๋๋ฐ TimePicker์ ๋ค์ด๊ฐ๋ ๊ฐ๋ค์ Listener์ default๋ก ์ค์ ํ hour์ minute, Boolean์ด ๋ค์ด๊ฐ๋ค.
private fun initChangeTimeButton() {
changeTimeButton.setOnClickListener {
val calendar = Calendar.getInstance() // ํ์ฌ ์๊ฐ์ ๊ฐ์ ธ์ค๋ ๋ฉ์๋
TimePickerDialog(this, { picker, hour, minute ->
val model = savedAlarmModel(hour, minute, false)
renderView(model)
}, calendar.get(Calendar.HOUR_OF_DAY), calendar.get(Calendar.MILLISECOND), false).show()
}
}
์์์ ์ฐ๊ฒฐํด์ค changeTimeButton
์ด ํธ์ถ๋๋ฉด ํ์ฌ ์๊ฐ์ ๊ฐ์ ธ์จ๋ค. Calendar ํด๋์ค์ getInstance
๋ฅผ ํตํด ๊ฐ์ ธ์จ๋ค. ์ดํ TimePickerDialog ์์ฑ์๋ฅผ ํตํด์ ํธ์ถํ๋๋ฐ ๋ ๋ฒ์งธ ์ธ์๋ก hour์ minute์ ์ค์ ํ ๋ค์ ์งํํ๋ ๊ฒ์ ๋ฐ๋๋ค. ๋๋ค์์ผ๋ก ์ค์ ํ hour์ minute์ ์ ์ฅํ๊ณ ์ด๋ฅผ ๋ค์ ๋ ๋๋งํด์ฃผ๋ ํจ์์ ๋ฃ์ด์คฌ๋ค. ์ธ ๋ฒ์งธ์ ๋ค ๋ฒ์งธ ์ธ์๋ก๋ TimePicker๋ฅผ ์ผฐ์ ๋ Default๋ก ๋ณด์ฌ์ค hour์ minute๋ฅผ ๋ฃ์ด์ค๋ค. ํ์ฌ ์๊ฐ์ ๋ฃ์ด์คฌ๋ค.
๋ค์์ผ๋ก ์์๋ณผ ๊ฒ์ TimePicker์์ ์ค์ ํ ์๊ฐ์ ์ค์ ์๊ฐ์ผ๋ก ๋ณ๊ฒฝํด์ฃผ๋ ๋ฉ์๋์ธ savedAlarmModel
๋ฉ์๋์ด๋ค. ์ฐ์ ๊ทธ ์ ์ ์๋ ๊ฐ์ฒด์ ๋ํ ์ ๋ณด๋ฅผ ๋ด๊ณ ์๋ ๋ฐ์ดํฐ ํด๋์ค AlarmDisplayModel
ํด๋์ค๋ฅผ ์ดํด๋ณด์.
package com.example.alarm
data class AlarmDisplayModel(
val hour: Int,
val minute: Int,
var onOff: Boolean
) {
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"
}
val onOffText: String
get() {
return if (onOff) "์๋ ๋๊ธฐ" else "์๋ ์ผ๊ธฐ"
}
fun makeDataForDB(): String {
return "$hour:$minute"
}
}
getter
๋ฅผ ํตํด์ ํน์ ํ ํํ๋ฅผ ๊ฐ๋๋ก ๊ตฌํํ๋ค. ๋ค์ ๋ฉ์๋๋ก ๋์ด๊ฐ๋ณด์.
private fun savedAlarmModel(hour: Int, minute: Int, onOff: Boolean): AlarmDisplayModel {
val model = AlarmDisplayModel(
hour = hour,
minute = minute,
onOff = false
)
val sharedPreferences = getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE)
with(sharedPreferences.edit()) {
putString(ALARM_KEY, model.makeDataForDB())
putBoolean(ONOFF_KEY, model.onOff)
commit()
}
return model
}
Data
ํด๋์ค์ ๋๊ฒจ์ฃผ์ด ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ค. ์ดํ SharedPreferences
์ ์ ์ฅํ๋ค. SharedPreferences
๋ ํ๋๋์คํฌ?์ ๋น์ทํ๋ค. ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ๊ธฐ ์ํ ๊ณต๊ฐ ์ ๋๋ก ์ดํดํ๋ฉด ๋๋ค.
์๋๋ก์ด๋ ๊ณต์๋ฌธ์ - SharedPreferences
์์ ์ฌ์ง์ ๊ณต์๋ฌธ์์์ ๊ฐ์ ธ์จ ๊ฒ์ด๊ณ key - value
๋ฅผ ์ด์ฉํด์ ๊ฐ์ ์ ์ฅํ๊ณ ๋ถ๋ฌ์ฌ ์ ์๋ค. ์ฌ๊ธฐ์ ์ ์ฅํ ๊ฐ์ ์๋์ผ๋ก ์ค์ ํ ์๊ฐ
๊ณผ On - Off
์ ๋ํ ์ ๋ณด์ด๋ค. ์ฐ์ private
๋ก ๊ณต๊ฐ์ ์์ฑํ๊ณ ์ดํ์ with
๊ณผ edit
์ ์ด์ฉํด์ ๊ฐ์ ์ ์ฅํ๋ค. ๊ฐ์ฒด ์์ฒด์์ ๋ฐ๋ก edit์ ํด์ ์ค์ ํ๋ ๊ฒฝ์ฐ commit()
๋ฉ์๋๋ฅผ ํธ์ถํ ํ์๊ฐ ์์ง๋ง with
์ ํตํด์ ๊ฐ์ ์ ์ฅํ๋ ๊ฒฝ์ฐ commit()
์ ๋ฌด์กฐ๊ฑด ํด์ค์ผํ๋ค. ๊ทธ๋์ผ ๋ณ๊ฒฝ๋ ๊ฐ์ด ์ ์ฅ๋๋ค.
companion object {
private const val ALARM_KEY = "alarm"
private const val ONOFF_KEY = "onOff"
private const val SHARED_PREFERENCES_NAME = "time"
private const val ALARM_REQUEST_CODE = 1000
}
ํค์ ๊ฒฝ์ฐ ์ ์ ๋ณ์๋ก ์ ์ธํด์คฌ๋๋ฐ ๊ทธ ์ด์ ๋ ๋ณ๊ฒฝ๋๋ฉด ์๋๊ธฐ ๋๋ฌธ์ด๋ค.
์์์ ์๊ฐ์ ์ ์ฅํ๋ ๋ฉ์๋๊น์ง ์๋ฃํ๋ค. ์ด์ ์ฑ์ ์คํํ์ ๋ ์ ์ฅ๋ ๊ณต๊ฐ์์ ์ค์ ํ ์๊ฐ์ ๊ฐ์ ธ์ค๋ฉด ๋๋ค.
private fun fetchDataFromSharedPreferences(): AlarmDisplayModel {
val sharedPreferences = getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE)
val timeDBValue = sharedPreferences.getString(ALARM_KEY, "12:00") ?: "12:00"
val onOffDBValue = sharedPreferences.getBoolean(ONOFF_KEY, false)
val alarmData = timeDBValue.split(":")
val alarmModel = AlarmDisplayModel(
hour = alarmData[0].toInt(),
minute = alarmData[1].toInt(),
onOff = onOffDBValue
)
return alarmModel
}
์ฐ์ sharedPreferences
๊ฐ์ฒด๋ฅผ ์์ฑํ๊ณ get
์ ํตํด value
๋ฅผ ๊ฐ์ ธ์จ๋ค. ์ด ๋ ๋ ๋ฒ์งธ ์ธ์๋ก default
๊ฐ์ ์ค์ ํ ์ ์๋ค. ํด๋น key์ ๊ฐ์ด ์๋ ๊ฒฝ์ฐ ์ด default ๊ฐ์ ๋ฆฌํดํ๊ฒ ๋๋ค. ์ ์ฅ๋ ๊ฐ์ ์ด์ฉํด์ AlarmDisplayModel
๊ฐ์ฒด๋ฅผ ๋ง๋ ๋ค. ์ต์ข
์ ์ผ๋ก ๊ฐ์ฒด๋ฅผ ๋ฆฌํดํด์ฃผ๋ ๊ฒ์ ๋ณผ ์ ์๋๋ฐ ์ต์ข
์ ์ผ๋ก ์ด๋ฅผ ๋ ๋๋งํ๋ ๊ณผ์ ์ ๊ฑฐ์น๋ค.
val model = fetchDataFromSharedPreferences()
renderView(model)
// ... ์ค๋ต
private fun renderView(model: AlarmDisplayModel) {
ampmTextView.apply {
text = model.ampmText
}
timeTextView.apply {
text = model.timeText
}
onOffButton.apply {
text = model.onOffText
tag = model
}
}
renderView
๋ฉ์๋์์ ์ค์ ๋ก ๋ ์ด์์๊ณผ ์ฐ๊ฒฐํด์ฃผ๋ ์์
์ ์ํํ๋ค.
์ง๊ธ๊น์ง ์๋์ ํด์ค ์๊ฐ์ ์ค์ ํ๋ค. ์ด์ ํด๋น ์๊ฐ์ ์๋ฆผ์ด ์ธ๋ฆฌ๊ฒ๋ ์ค์ ํ๋ฉด ๋๋ค.
private fun initOnOffButton() {
val onOffButton = findViewById<Button>(R.id.onOffButton)
onOffButton.setOnClickListener {
val model = it.tag as? AlarmDisplayModel ?: return@setOnClickListener
val newModel = saveAlarmModel(model.hour, model.minute, model.onOff.not())
renderView(newModel)
if (newModel.onOff) {
// ์ผ์ง ๊ฒฝ์ฐ -> ์๋์ ๋ฑ๋ก
val calendar = Calendar.getInstance().apply {
set(Calendar.HOUR_OF_DAY, newModel.hour)
set(Calendar.MINUTE, newModel.minute)
if (before(Calendar.getInstance())) {
add(Calendar.DATE, 1)
}
}
val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
val intent = Intent(this, AlarmReceiver::class.java)
val pendingIntent = PendingIntent.getBroadcast(this, ALARM_REQUEST_CODE,
intent, PendingIntent.FLAG_UPDATE_CURRENT)
alarmManager.setExact(
AlarmManager.RTC_WAKEUP,
calendar.timeInMillis,
pendingIntent
)
} else {
cancelAlarm()
}
}
}
์๋์ ๋ํ ํด๋์ค๋ BroadcastReceiver
๋ฅผ ์์๋ฐ์์ ๊ตฌํํ๋ค. BroadcastReceiver๋ ๋ง ๊ทธ๋๋ก ํน์ ํ์๊ฐ ๋ฐ์ํ์ ๋ ์๋ํ๋ ํด๋์ค์ด๋ค.
package com.example.alarm
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.os.Build
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
class AlarmReceiver: BroadcastReceiver() {
companion object {
const val NOTIFICATION_ID = 100
const val NOTIFICATION_CHANNEL_ID = "1000"
}
override fun onReceive(context: Context, intent: Intent) {
createNotificationChannel(context)
notifyNotification(context)
}
private fun createNotificationChannel(context: Context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val notificationChannel = NotificationChannel(
NOTIFICATION_CHANNEL_ID,
"๊ธฐ์ ์๋",
NotificationManager.IMPORTANCE_HIGH
)
NotificationManagerCompat.from(context).createNotificationChannel(notificationChannel)
}
}
private fun notifyNotification(context: Context) {
with(NotificationManagerCompat.from(context)) {
val build = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID)
.setContentTitle("์๋")
.setContentText("์ผ์ด๋ ์๊ฐ์
๋๋ค.")
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setPriority(NotificationCompat.PRIORITY_HIGH)
notify(NOTIFICATION_ID, build.build())
}
}
}
์๋์ด ์๋ํด์ผํ ๋ ์ด ํด๋์ค๋ฅผ ์๋์ํค๋ ค๊ณ ํ๋ค.
Nofication
๊ณผ Broadcasting
์ ๊ดํ ๊ณต์๋ฌธ์๋ฅผ ๊ฐ์ ธ์๋ค.
์๋๋ก์ด๋ ๊ณต์ ๋ฌธ์ - Notification
์๋๋ก์ด๋ ๊ณต์ ๋ฌธ์ - Broadcasting
ํด๋น ์๊ฐ์ ์๋ฆผ์ด ์ธ๋ฆฌ๋ ๊ณณ์ ๋ณผ ์ ์๋ค.