Apple is Apple

일일 회고

 

프로젝트 정규일의 마지막 날이 다가왔다. 진짜진짜 5일이 순식간에 지나가 버렸다.

 

기능 구현은 거의 다 완성했고, 이제 버그를 찾아서 수정하는 작업을 위주로 하고, 발표 준비를 하면 될 것 같다.

 

주말도? 열심히 해보자!


오늘의 키워드

  • 프로젝트 버그 수정
  • 알람 기능 추가

프로젝트 버그 수정

1. 키 값 중복으로 인한 데이터 송수신 불가

페이지끼리 데이터를 주고 받는 부분이 두 군데가 있는데, 두 군데의 키 값이 같아서 데이터가 넘어오지 않는 문제가 있었다.

한 군데의 키 값을 바꾸어 주어 문제를 해결해였다.

 

2, 버그는 아니지만 데이터 타입을 수정을 하였다.

갤러리에서 이미지를 가져오는 부분이 있어 이미지 ID 값으로는 유지하기가 힘들게 되어 Uri를 통해 이미지를 보여주도록 데이터 클래스의 이미지 타입을 URI로 바꾸어주었다.

 

기본이미지는 튜터님이 제시해주신 drawable -> Uri 컨버팅 코드를 활용하여 Uri로 바꿀 수 있었다.

 

 

알람 기능 추가

TimePickerDialog와 Notification, AlarmManager를 통해 특정 시간에 연락 리마인드 알림을 해줄 수 있는 기능을 구현하였다.

 

먼저, TimePickerDialog로 시간을 정할 수 있게 한다.

그리고 TimePicerDialog에 Listener를 달아주어야하는데, 시간 설정을 했을 때 어떤 동작을 할 지에 대한 리스너이다.

TimePickerDialog.OnTimeSetListener{ view, hourOfDay, minute ->
	// 기능 구현
}

TimePickerDialog.OnTimeSetListener를 구현하면 된다.

내부에서 AlarmManager를 통해 alarm을 설정해 줄 수 있다.

 

AlarmManager는 아래 특징을 갖고 있다.

  • 지정된 시간에 일정간격으로 App 알람 이벤트 받도록 설정 가능
  • 알람은 Intent로 등록되며, 시스템에 의해 BroadcastReceiver로 인텐트가 전달된다.
  • AlarmManager가 이벤트를 보내기 때문에, 내 App이 실행 중이지 않더라도 알람을 받아 어떤 작업을 처리하도록 구현할 수 있다.
private val listener =
        TimePickerDialog.OnTimeSetListener { _, hourOfDay, minute ->
            // 고유 코드를 위한 정보 리스트
            val idList = CallObjectData.list.map { it.id }
            // 알람 매니저 선언
            val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
            // 현재 시간과 알람이 울릴 시간 선언
            val (timeNow, alarmTime) = getCurrentTimeAndAlarmTime(hourOfDay, minute)
            // 몇시간 차이나는지
            val hourDifference = timeNow.get(Calendar.HOUR_OF_DAY) - hourOfDay
            // 몇 분 차이나는지
            val minuteDifference = minute - timeNow.get(Calendar.MINUTE)
            // 각자의 고유 코드 필요 - 고유 코드가 없으면 데이터가 달라져도 한 데이터에 대해서만 알람이 설정됨
            val code = idList.indexOf(obj.id)

            val bundle = Bundle().apply {
                putParcelable("obj", obj)
            }

            // 알람을 받을 AlarmReceiver intent 선언
            val receiverIntent = Intent(context, AlarmReceiver::class.java).apply {
                putExtra("bundle", bundle)
            }
            // 알람은 BraodCastReceiver에 의해 동작하므로 getBroadcast를 통해 
            // 알람 신호를 보내 준다.
            val pendingIntent = PendingIntent.getBroadcast(
                context,
                code,
                receiverIntent,
                PendingIntent.FLAG_MUTABLE
            )

			// 현재 시간보다 설정시간이 느리다면 알람을 안울리도록
            if(timeNow.timeInMillis >= alarmTime.timeInMillis) {
                context.toast("설정 시간은 현재 시간 이후로 설정해주세요.")
                return@OnTimeSetListener
            }

            context.toast(
                "${obj.name}님께 연락 할 수 있도록 ${hourDifference * 60 + minuteDifference}" +
                        "분 이후 알림"
            )
            
            // AlarmManager에 알람을 등록
            // alarmTime.timeInMillis --> 진짜 알람 // 일단은 5초 있다가
            alarmManager.set(AlarmManager.RTC_WAKEUP, timeNow.timeInMillis + 5, pendingIntent)
        }

 

manifest에 BroadCastReceiver를 설정해준다.

"android.intent.action.BOOT_COMPLETED"는 부팅후에도 알람이 실행 될 수 있게 해준다.

<receiver android:name=".view.main.notification.AlarmReceiver"
    android:enabled="true"
    android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />
    </intent-filter>
</receiver>

 

그리고 BroadcastReceiver를 구현해준다.

class AlarmReceiver : BroadcastReceiver() {
    private lateinit var manager: NotificationManager
    private val list = CallObjectData.list.map { it.id }

    @SuppressLint("UnsafeProtectedBroadcastReceiver")
    override fun onReceive(context: Context, intent: Intent) {
        createNotificationChannel(context)
        // notification manager
        manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        val bundle = intent.getBundleExtra("bundle")

        val model = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            bundle?.getParcelable("obj", CallingObject::class.java)
        } else {
            bundle?.getParcelable("obj")
        }
        // 알람 클릭시 넘어갈 인텐트 준비
        val alarmIntent = Intent(context, MainActivity::class.java)

		// pendingIntent로 만듬
        val pendingIntent =
            PendingIntent.getActivity(context, 101, alarmIntent, PendingIntent.FLAG_IMMUTABLE)

        val builder = createNotification(context, model, pendingIntent)

        // 각자의 noti에도 고유값필요
        val id = list.indexOf(model?.id)

        if (ActivityCompat.checkSelfPermission(
                context,
                Manifest.permission.POST_NOTIFICATIONS
            ) != PackageManager.PERMISSION_GRANTED
        ) {
            return
        }
        NotificationManagerCompat.from(context).notify(id, builder)
    }

    private fun createNotification(
        context: Context,
        model: CallingObject?,
        pendingIntent: PendingIntent
    ): Notification {
        val builder = NotificationCompat.Builder(context, CHANNEL_ID)
        builder.apply {
            //알림창 제목
            setSmallIcon(R.drawable.notification_icon)
            setContentTitle("연락처 알림")
            setContentText("${model?.name}님에게 전화를 걸 시간입니다.")
            setContentIntent(pendingIntent)
            setChannelId(CHANNEL_ID)
            //알림창 터치시 자동 삭제
            setAutoCancel(true)
            setContentIntent(pendingIntent)
        }

        return builder.build()
    }

    private fun createNotificationChannel(context: Context?) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channel = NotificationChannel(
                CHANNEL_ID,
                CHANNEL_NAME,
                NotificationManager.IMPORTANCE_DEFAULT
            ).apply {
                description = "this is YOU & ME"
                val uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
                val audioAttributes = AudioAttributes.Builder()
                    .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
                    .setUsage(AudioAttributes.USAGE_ALARM)
                    .build()
                setSound(uri, audioAttributes)
                enableVibration(true)
            }
            channel.description = CHANNEL_DESCRIPTION
            (context?.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager)
                .createNotificationChannel(channel)
        }
    }

    companion object {
        private const val CHANNEL_NAME = "Call Notification"
        private const val CHANNEL_DESCRIPTION = "Call Alarm"
        private const val CHANNEL_ID = "channel id"
    }
}

BroadcastReceiver를 상속받고 onReceive를 오버라이딩을한다.

onReceive메소드는 Broadcast가 되었을 때, 호출 되는 함수이다.

onReceive내에서 notification을 실행하는 코드를 작성한다.

profile

Apple is Apple

@mjjjjjj