[TIL][내일배움캠프]

[내일배움캠프][TIL] 24.01.09 (화) - Dialog&Notification, Android 앱개발 숙련 실습과제 2일차

kimlaurant 2024. 1. 9. 21:27
1. Dialog & Notification

 

과제를 진행하기 이전에, 과제를 진행하는 데에 필요한 다이얼로그와 알림을 알아보도록 하자.

 

다이얼로그(Dialog)

 

다이얼로그는 이름만 봐서는 굉장히 생소한 말인데 그림을 보면 단번에 이해가 된다.

얘가 바로 다이얼로그다.

이렇듯 다이얼로그는 사용자에게 결정을 내리거나 추가정보를 입력하라는 메시지를 표시하는 작은창을 말한다.

 

○ 다이얼로그 구성요소

1) 제목(setTitle)

선택 사항이며 콘텐츠 영역에 상세한 메시지, 목록 또는 맞춤 레이아웃이 채워져 있는 경우에 사용

 

2) 콘텐츠 영역

메시지(setMessage), 목록 또는 다른 맞춤 레이아웃을 표시 할 수 있음

 

3) 작업 버튼(setButton)

실질적으로 다이얼로그의 행동을 결정하는 영역. 보통 사용하는 확인, 취소 외에도 여러가지 버튼을 사용할 수 있지만, 최대 3개까지만 배치할 수 있음.

 

 

○ 다이얼로그 구현 코드

// 1. 기본 다이얼로그
        binding.btn1Alert.setOnClickListener {
            var builder = AlertDialog.Builder(this)
            builder.setTitle("기본 다이얼로그 타이틀")
            builder.setMessage("기본 다이얼로그 메세지")
            builder.setIcon(R.mipmap.ic_launcher)

            // 버튼 클릭시에 무슨 작업을 할 것인가!
            val listener = object : DialogInterface.OnClickListener {
                override fun onClick(p0: DialogInterface?, p1: Int) {
                    when (p1) {
                        DialogInterface.BUTTON_POSITIVE ->
                            binding.tvTitle.text = "BUTTON_POSITIVE"
                        DialogInterface.BUTTON_NEUTRAL ->
                            binding.tvTitle.text = "BUTTON_NEUTRAL"
                        DialogInterface.BUTTON_NEGATIVE ->
                            binding.tvTitle.text = "BUTTON_NEGATIVE"
                    }
                }
            }

            builder.setPositiveButton("Positive", listener)
            builder.setNegativeButton("Negative", listener)
            builder.setNeutralButton("Neutral", listener)

            builder.show()
        }

 

 

알림 (Notification)

알림 (Notification)

알림(Notification)은 앱의 UI와 별도로 사용자에게 앱과 관련한 정보를 보여주는 기능이다.

Android 8.0이상의 경우는 알림을 만들기 전에 알림 채널을 먼저 만들어야 한다.

 

※알림 채널 코드 (Android 8.0 이상)

private val myNotificationID = 1
private val channelID = "default"

private fun createNotificationChannel() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { // Android 8.0
        val channel = NotificationChannel(channelID, "default channel",
            NotificationManager.IMPORTANCE_DEFAULT)
        channel.description = "description text of this channel."
        val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        notificationManager.createNotificationChannel(channel)
    }
}

 

○ 알림 생성

  • 1) NotificationCompat.Builder 객체에서 알림에 대한 UI정보와 작업을 지정
    • setSmallIcon() : 작은 아이콘
    • setContentTitle() : 제목
    • setContentText() : 세부텍스
  • 2) NotificationCompat.Builder.build()호출
    • Notification 객체를 반환
  • 3) NotificationManagerCompat.notify()를 호출해서 시스템에 Notification객체를 전달
private val myNotificationID = 1

private fun showNotification() {
    val builder = NotificationCompat.Builder(this, channelID)
        .setSmallIcon(R.mipmap.ic_launcher)
        .setContentTitle("title")
        .setContentText("notification text")
        .setPriority(NotificationCompat.PRIORITY_DEFAULT)
    NotificationManagerCompat.from(this).notify(myNotificationID, builder.build())
}

 

○ 예제

class MainActivity : AppCompatActivity() {
    private val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)
        binding.notificationButton.setOnClickListener{
            notification()
        }
    }

    fun notification(){
        val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager

        val builder: NotificationCompat.Builder
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
            // 26 버전 이상
            val channelId="one-channel"
            val channelName="My Channel One"
            val channel = NotificationChannel(
                channelId,
                channelName,
                NotificationManager.IMPORTANCE_DEFAULT
            ).apply {
                // 채널에 다양한 정보 설정
                description = "My Channel One Description"
                setShowBadge(true)
                val uri: 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)
            }
            // 채널을 NotificationManager에 등록
            manager.createNotificationChannel(channel)

            // 채널을 이용하여 builder 생성
            builder = NotificationCompat.Builder(this, channelId)

        }else {
            // 26 버전 이하
            builder = NotificationCompat.Builder(this)
        }

				val bitmap = BitmapFactory.decodeResource(resources, R.drawable.flower)
        val intent = Intent(this, SecondActivity::class.java)
        intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
        val pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
        // 알림의 기본 정보
        builder.run {
            setSmallIcon(R.mipmap.ic_launcher)
            setWhen(System.currentTimeMillis())
            setContentTitle("새로운 알림입니다.")
            setContentText("알림이 잘 보이시나요.")
            setStyle(NotificationCompat.BigTextStyle()
                .bigText("이것은 긴텍스트 샘플입니다. 아주 긴 텍스트를 쓸때는 여기다 하면 됩니다.이것은 긴텍스트 샘플입니다. 
아주 긴 텍스트를 쓸때는 여기다 하면 됩니다.이것은 긴텍스트 샘플입니다. 아주 긴 텍스트를 쓸때는 여기다 하면 됩니다."))
            setLargeIcon(bitmap)
//            setStyle(NotificationCompat.BigPictureStyle()
//                    .bigPicture(bitmap)
//                    .bigLargeIcon(null))  // hide largeIcon while expanding
            addAction(R.mipmap.ic_launcher, "Action", pendingIntent)
        }


        manager.notify(11, builder.build())
    }

}

 

 

2. Android 앱개발 숙련 실습과제 2일차

 

오늘은 어제 만든 과제에 이어서 아직 구현하지 못 한 기능들을 마저 구현해보자.

 

아이템들 사이에 회색 라인을 추가해서 구분

 

RecyclerView는 자체적으로 라인을 그릴 수 없어서 MainActivity에 다음 코드를 추가해야 한다.

binding.recyclerView.addItemDecoration(DividerItemDecoration(this, LinearLayoutManager.VERTICAL))

 

ViewBinding을 했기 때문에 앞에 binding을 붙인 건 덤이다.

그 결과는 꽤 잘 나왔다.

 

상세페이지 이동 & Parcelize를 이용한 데이터 전달

 

어제는 Data Class를 Parcelize한 것까지만 완성시키고, 정작 상세페이지로 이동하는 건 완성시키지 못했다.

그래서 오늘은 이걸 완성시켜봤다.

 

adapter.itemClick = object : MyAdapter.ItemClick {
	override fun onClick(view: View, position: Int) {
		val selectedItem = dataList[position]

		val intent = Intent(this@MainActivity, DetailActivity::class.java).apply{
			putExtra("selectedItem", selectedItem)
		}
		startActivity(intent)
	}
}

어제 MyAdapter.kt에 만들어놨던 ItemClick 인터페이스와 onClick 메소드를 불러온 다음, 클릭한 상품의 position을 selecedItem에 저장하고 그 정보들을 intent로 보낸 다음에 상세페이지(DetailActivity)를 실행시킨다.

 

그리고 DetailActivity에서는

val dataList = intent.getParcelableExtra<MyItem>("selectedItem")

이걸로 MainActivity에서 보낸 데이터를 받는다.

 

참고로, 상세페이지에서는 RecyclerView를 사용하지 않기 때문에 따로 Adapter를 통해서 전달받지 않으므로 쌩으로 데이터들을 활용했다.

binding.ivItem.setImageResource(dataList!!.aIcon)
binding.txtdetailName.text = dataList.aName

 

다이얼로그 & 알림

 

다이얼로그는 '뒤로가기' 버튼을 누를 때 띄워야 하므로 setOnClickListener를 쓰지 않고 다른 방법을 사용했다.

    @SuppressLint("MissingSuperCall")
    override fun onBackPressed() {
        val builder = AlertDialog.Builder(this)
        builder.setTitle("종료")
        builder.setIcon(R.mipmap.chat)
        builder.setMessage("정말 종료하시겠습니까?")

        val listener = DialogInterface.OnClickListener { _, p0 ->
            if (p0 == DialogInterface.BUTTON_POSITIVE) finish()
        }
        builder.setPositiveButton("확인", listener)
        builder.setNegativeButton("취소", null)
        builder.show()
    }

 

알림은 종소리를 누를 때 띄워야 하므로 우선은 onCreate() 함수 안에 다음 코드를 집어넣는다.

binding.notificationBell.setOnClickListener {
	notification()
}

 

그리고 notification() 함수는 다음과 같이 만들었다.

    fun notification() {
        val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager

        var builder: NotificationCompat.Builder
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channelID = "one-channel"
            val channelName = "My Channel One"
            val channel = NotificationChannel(channelID, channelName, NotificationManager.IMPORTANCE_DEFAULT).apply{
                description = "My Channel One Description"
                setShowBadge(true)
                val uri: 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)
            }
            manager.createNotificationChannel(channel)
            builder = NotificationCompat.Builder(this, channelID)
        } else {
            builder = NotificationCompat.Builder(this)
        }

        builder.run {
            setWhen(System.currentTimeMillis())
            setContentTitle("키워드 알림")
            setContentText("설정한 키워드에 대한 알림이 도착했습니다!!")
        }

        manager.notify(11, builder.build())
    }

 

 

트러블슈팅 & 피드백

 

1. 트러블슈팅

  • 현재 종 버튼을 누르면 앱이 비정상적으로 종료되는 이슈가 생겼다. 분명 저기 알람 코드에 뭔가를 빼먹어서 null값이 생긴 것이 아닐까 추측하는 중이다.
  • 메인페이지에서 OnClick 시 상세페이지로 넘어가는 이벤트를 적는데 자꾸 오류가 생기길래 왜 이런 문제가 생기는 건가 하고 찾는 데에 시간을 꽤나 많이 소비했다. 결국 맨 처음에 dataList[position]의 변수를 선언해야 intent가 제대로 작동하는 걸 알아내서 겨우 진전할 수 있었다.

 

2. 피드백

  • 결국 DB를 깔끔하게 만드는 방법은 포기하고 MainActivity 안에 집어넣기로 했다. 심지어 하다보니 DetailActivity에서는 다른 종류의 데이터를 요구해서 몇 가지를 더 추가했더니 아주 그냥 난잡함의 끝이 되어버렸다.
  • 원래는 상세페이지 UI 골자를 완성시킨 다음에 메인페이지의 디테일적인 부분을 만지려 했는데 순서가 반대로 되어버렸다. Parcelize를 완성시키려다보니 어쩌다 다이얼로그, 알림도 스윽스윽 만들었고 그러다보니 상세페이지 UI 완성이 늦어져버렸다. 애초부터 계획 순서를 잘못 잡았는지 아니면 내가 삼천포로 빠진 건지를 모르겠다.