[TIL] Android 앱 개발 숙련 : Fragment와 다이얼로그, 알람

지혜·2024년 1월 5일

Android_TIL

목록 보기
32/70

✏240105 금요일 TIL(Today I learned) 오늘 배운 것

📖Fragment(프래그먼트)

  • 액티비티 안에서 특정영역만 따로 동작하는 인터페이스. 독립적으로 동작할 수 없다.
  • 액티비티로 화면을 계속 이동하는 것보다 자원이용량이 적고, 속도가 빠르기 때문에 Fragment는 많이 사용된다.
    => Fragment를 많이 사용하면, 액티비티 복잡도를 줄일 수 있고, 재사용성도 높아지기 때문에 효율적이다.
  • 액티비티와 비슷하게 하나의 Kotlin 소스 파일(Fragment의 서브클래스 생성)과 그와 연계된 하나의 XML레이아웃(onCreateView()콜백 메서드 구현 필수)로 정의한다. New>Fragment를 선택하면 자동으로 생성된다.

[프래그먼트 추가하는 방법]

  1. [정적추가]레이아웃 파일 안에서 <fragment>태그를 사용하여 선언할 수 있다.
    : android:name을 통해 레이아웃 안에서 인스턴스화할 Fragment 클래스를 지정한다.
    +프래그먼트 복구를 위해 고유 식별자가 필요하기 때문에 ID가 필수이다.
    • 프래그먼트 ID를 제공하는 방법
      1.고유한 ID와 함께 android:id 속성을 제공.
      2.고유한 문자열과 함께 android:tag 속성을 제공.
      3.위의 두 가지 중 어느 것도 제공하지 않으면, 시스템은 컨테이너 뷰의 ID를 사용한다.
  1. [동적추가] Kotlin 파일에서 코드로 추가할 수 있다.
    : build.gradle.kts(Module)파일안에 있는 의존성에 implementation ("androidx.fragment:fragment-ktx:1.6.2")를 추가해야 사용할 수 있다.
    //사용자 상호작용에 응답해 Fragment를 추가하거나 삭제하는등 작업을 할 수 있게 해주는 매니저
    supportFragmentManager.commit {
    		  //어느프레임 레이아웃에 띄울것이냐, 어떤프래그먼트냐
              replace(R.id.frameLayout, frag)
              //애니메이션과 전환이 올바르게 작동하도록 트랜잭션과 관련된 프래그먼트의 상태 변경을 최적화
              setReorderingAllowed(true)
              //뒤로가기 버튼 클릭시 다음 액션 (이전 fragment로 가거나 앱이 종료되거나)
              addToBackStack("")
          }
    

[프레그먼트의 생명주기 메서드]

  • 각 생명주기 단계에 맞춰 적절한 작업을 수행하여, 메모리 누수를 방지하고 어플리케이션 성능의 최적화가 가능해진다.
  1. onAttach()
    : 프래그먼트가 액티비티에 연결될 때 호출. 아직 액티비티와 완전히 연결되지 않은 상태.

  2. onCreate()
    : 프레그먼트가 생성될 때 호출. 초기화 작업, 리소스 바인딩 등을 수행하기에 적합하다.
    => 다른곳에서 전달되어 Bundle에 저장된 데이터를 get하여 저장한다.

  3. onCreateView()
    : 프래그먼트의 레이아웃이 inflate되는 지점. 뷰를 생성하고, 레이아웃을 설정한다.
    => return inflater.inflate(R.layout.프레그먼트레이아웃, container, false) : 프레그먼트레이아웃에 해당하는 xml파일을 그대로 보여준다.
    => return binding.root : 바인딩해놓은 xml파일이 보여진다.

  4. onActivityCreated()
    : 액티비티의 onCreate() 메서드가 완료된 후 호출. 액티비티와 프래그먼트의 뷰가 모두 생성된 상태이므로, 뷰와 관련된 초기화를 수행한다.
    =>액티비티와의 상호작용이 필요한 경우에 사용하면 좋다.
    +onViewCreated() : 프레그먼트 뷰가 완전히 생성된 후 호출. 뷰와 관련된 초기화 작업을 수행한다.
    => 뷰의 이벤트 리스너를 설정하거나, 프래그면트 뷰에 대한 참조를 얻을 때 사용하면 좋다.

  1. onStart()
    : 프래그먼트가 사용자에게 보여질 준비가 되었을 때 호출. 필요한 리소스를 할당하거나, 애니메이션을 시작할 수 있다.

  2. onResume()
    : 프래그먼트가 사용자와 상호작용할 수 있는 상태가 되었을 때 호출. 프래그먼트가 포그라운드에 있을 때 실행되는 작업을 여기서 처리한다.

  3. onPause()
    : 프래그먼트가 일시정지될 때 호출. 상태 저장, 스레드 중지 등의 작업을 수행한다.

  4. onStop()
    : 프래그먼트가 더 이상 사용자에게 보이지 않을 때 호출. 리소스 해제, 스레드 정지 등을 수행한다.

  5. onDestroyView()
    : 프래그먼트의 뷰와 관련된 리소스를 정리할 때 호출.

  6. onDestroy()
    : 프래그먼트가 파괴될 때 호출. 프래그먼트의 상태를 정리하고, 모든 리소스를 해제한다.

  7. onDetach()
    : 프래그먼트가 액티비티로부터 분리될 때 호출. 프래그먼트가 액티비티와의 모든 연결을 해제한다.


[프래그먼트의 데이터 전달]

  1. Activity → Fragment
    :액티비티에서 프래그먼트로 이동할 때 메소드(newInstance 메소드)를 통해 데이터를 담아서 전달한 후 Bundle객체로 프레그먼트의 인자로 설정후, 이 인자를 프레그먼트에서 사용.

  2. Fragment → Fragment
    : 1번 방법과 유사하다. 프래그먼트에서 다른 프래그먼트로 이동할 때 메소드(newInstance 메소드)를 통해 데이터를 담아서 전달한 후 Bundle객체로 프레그먼트의 인자로 설정후, 이 인자를 프레그먼트에서 사용.

  3. Fragment → Activity
    : 콜백인터페이스를 통해 데이터전달을 해야함. 프레그먼트에서 인터페이스로 데이터를 전달하면 인터페이스는 액티비티에서 구현.


📖다이얼로그(Dialog)

  • 화면을 가득 채우지 않으며 보통은 사용자가 다음으로 계속 진행하기 전에 조치(결정을 내리거나, 추가정보 입력 메시지 등)를 취해야 하는 모달 이벤트에 사용.

[다이얼로그 구조]

  1. 제목 : 선택 사항이며 콘텐츠 영역에 상세한 메시지,목록 또는 맞춤 레이아웃이 채워져 있는 경우에만 사용해야 한다. 단순한 메시지 또는 질문을 나타내야 하는 경우 제목은 없어도 된다.
    => setTitle( ), setIcon( )

  2. 콘텐츠 영역 : 메시지,목록 또는 다른 맞춤 레이아웃을 표시 할 수 있다.
    => setMessage( )

  3. 작업 버튼 : 대화 상자 하나에 작업 버튼이 세 개를 초과하면 안된다.
    => setPositiveButton( ), setNegativeButton( ), setNeutralButton( )

[다이얼로그 종류]

  • 필요에 따라 사용하면 된다.
  1. 기본 다이얼로그

    binding.btn1Alert.setOnClickListener {
               var builder = AlertDialog.Builder(this)
               builder.setTitle("기본 다이얼로그 타이틀")
               builder.setMessage("기본 다이얼로그 메세지")
               builder.setIcon(R.mipmap.ic_launcher)
    
               // 버튼 클릭시에 무슨 작업을 할 지에 대해 따로 listener를 생성
               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()
           }
    

  2. 커스텀 다이얼로그 : res>layout 안에 따로 xml파일을 만들어서 연결하여 사용 가능.

    binding.btn2Custom.setOnClickListener {
               val builder = AlertDialog.Builder(this)
               builder.setTitle("커스텀 다이얼로그")
               builder.setIcon(R.mipmap.ic_launcher)
    
    			//따로 만든 dialog.xml을 연결한다.
               val v1 = layoutInflater.inflate(R.layout.dialog, null)
               builder.setView(v1)
    
               // p0에 해당 AlertDialog가 들어온다. findViewById를 통해 view를 가져와서 사용
               val listener = DialogInterface.OnClickListener { p0, p1 ->
                   val alert = p0 as AlertDialog
                   val edit1: EditText? = alert.findViewById<EditText>(R.id.editText)
                   val edit2: EditText? = alert.findViewById<EditText>(R.id.editText2)
    
                   binding.tvTitle.text = "이름 : ${edit1?.text}"
                   binding.tvTitle.append(" / 나이 : ${edit2?.text}")
               }
    
               builder.setPositiveButton("확인", listener)
               builder.setNegativeButton("취소", null)
    
               builder.show()
           }

  3. 날짜 다이얼로그(DatePickerDialog)

    binding.btn3Date.setOnClickListener {
               val calendar = Calendar.getInstance()
               val year = calendar.get(Calendar.YEAR)
               val month = calendar.get(Calendar.MONTH)
               val day = calendar.get(Calendar.DAY_OF_MONTH)
    
               val listener = DatePickerDialog.OnDateSetListener { datePicker, i, i2, i3 ->
                   // i년 i2월 i3일
                   // 월의 경우 0부터 시작하기 떄문에 +1을 꼭 해줘야한다.
                   binding.tvTitle.text = "${i}${i2 + 1}${i3}일"
               }
    
               var picker = DatePickerDialog(this, listener, year, month, day)
               picker.show()
           }

  4. 시간 다이얼로그(TimePickerDialog)

    binding.btn4Time.setOnClickListener {
              val calendar = Calendar.getInstance()
              val hour = calendar.get(Calendar.HOUR)
              val minute = calendar.get(Calendar.MINUTE)
    
              val listener = TimePickerDialog.OnTimeSetListener { timePicker, i, i2 ->
                  binding.tvTitle.text = "${i}${i2}분"
              }
    
              val picker = TimePickerDialog(this, listener, hour, minute, false)
              // false 대신 true를 선택하면 24시간 모양.
              
              picker.show()
          }

  5. 진행 다이얼로그(ProgressDialog)

    // 커스텀 다이얼로그와 유사한 방식. 따로 xml파일을 만들어서 연결해줘야한다.
    binding.btn5Porgress.setOnClickListener {
               val builder = AlertDialog.Builder(this)
               builder.setTitle("프로그래스바")
               builder.setIcon(R.mipmap.ic_launcher)
    
    		   // progressbar.xml을 연결해준다.
              //(xml 안에서 <ProgressBar> 태그를 사용하여 레이아웃모양을 만들 수 있다.)
               val v1 = layoutInflater.inflate(R.layout.progressbar, null)
               builder.setView(v1)
    
               builder.show()
           }
    //Deprecate된 기능.
    binding.btn5Porgress.setOnClickListener {
               // 권장하진 않지만 사용은 가능하다.
               pro = ProgressDialog.show(this, "타이틀입니다.", "메시지입니다.")
    
               // 핸들러를 통해서 종료 작업을 한다.
               val handler = Handler()
               val thread = Runnable { pro?.cancel() }
               handler.postDelayed(thread, 5000) // 딜레이는 5초
           }

[DialogFragment]

  • 다이얼로그를 보여주는데 활용되는 프래그먼트. 다이얼로그 객체를 포함하고, 프래그먼트의 상태에 따라 적절히 화면에 보여준다.
  • 일반적인 다이얼로그 클래스와 달리 화면 회전 등의 액티비티 파괴 시에도 상태를 유지하는데 훨씬 안정적이다.
  • Fragment로 사용이 가능하고, Diaglog로도 사용이 가능하다.
  • onCreatDialog 메소드를 오버라이드 해야하고, 이를 통해 xml파일에 따로 Positive?Negative Button을 만들 필요없이 다이얼로그가 보여질 때의 화면과 이벤트를 정의할 수 있다. 또, 이 메서드를 통해 일반적인 다이얼로그와 동일한 방식으로 다이얼로그를 구성할 수 있다.
  • 다이얼로그 클래스보다 DialogFragment의 사용을 권장한다.

📖알림(Notification)

  • 앱의 UI와 별도로 사용자에게 앱과 관련한 정보를 보여주는 기능. 보통 단말기 상단 부분에 표시되고, 앱 아이콘의 배지로도 표시(Android 8.0부터)된다.
  • 알림을 터치하여 해당 앱을 열 수 있고, 바로 간단한 작업(문자 답하기 등)을 할 수 있다.(Android 7.0부터)
  • 사용자 경험을 고려하여 적절한 시점에 수행될 수 있도록 해야하며, 알림의 가치를 이해할 수 있도록 설득력이 있어야만 한다.

1. 알림 채널

  • 알림을 그룹하여 알림 활성화나 방식을 변경 할 수 있게 해준다.
  • Android 8.0이상에서는 알림을 만들기 전에 알림 채널을 먼저 만들어야한다.
    private val myNotificationID = 1
    private val channelID = "default"
    private fun createNotificationChannel() {
    	//Android 8.0이상인지 확인해야한다.
       if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
           val channel = NotificationChannel(channelID, "default channel",
               NotificationManager.IMPORTANCE_DEFAULT)
               //채널 중요도(Android 8.0이상)
               
           channel.description = "description text of this channel."
           val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
           notificationManager.createNotificationChannel(channel)
       }
    }

2. 알림 생성

  • NotificationCompat.Builder 객체에서 알림에 대한 UI정보와 작업을 지정한다.
    private val myNotificationID = 1
    private fun showNotification() {
    					// NotificationCompat.Builder.build()호출하고, Notification객체를 반환
       val builder = NotificationCompat.Builder(this, channelID)
       	   //작은 아이콘
           .setSmallIcon(R.mipmap.ic_launcher)
           
           // 제목
           .setContentTitle("title")
           
           // 세부텍스트
           .setContentText("notification text")
           
           //알림 우선순위 설정 (Android 7.1이하)
           .setPriority(NotificationCompat.PRIORITY_DEFAULT)
           
       //NotificationManagerCompat.notify()를 호출해서 시스템에 Notification객체를 전달
       NotificationManagerCompat.from(this).notify(myNotificationID, builder.build())
    }

3. 알림 중요도

  • 채널 중요도(IMPORTANCE)와, 알림 우선순위(PRIORITY)의 차이는 1.알림채널/2.알림생성 참고.
  • HIGH / DEFAULT / LOW / MIN

4. 알림확장뷰 설정 : builder에서 추가로 작성해주면 된다.

  • 긴 텍스트
    setStyle(NotificationCompat.BigTextStyle()
                   .bigText( "~")
  • 이미지
    	// 넣을 이미지를 bitmap으로 builder 바깥에 설정
    	val bitmap = BitmapFactory.decodeResource(resources, R.drawable.flower)
    setStyle(NotificationCompat.BigPictureStyle()
    		.bigPicture(bitmap)
    		.bigLargeIcon(null))  // hide largeIcon while expanding
  • 버튼추가 : 따로 버튼을 클릭하여 Intent를 통해서 Activity나 Broadcast를 연결시킬 수 있다.
    //연결해줄 intent를 builder 바깥에 설정
    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)
     addAction(R.mipmap.ic_launcher, "Action", pendingIntent)
  • 프로그레스바 추가도 가능하다.

5. 알림에 액티비티 연결

  • 알람을 터치하면 알람은 사라지고 액티비티가 실행된다. 이때, 메인액티비티가 아닌, 필요한 액티비티로 이동할 수 있다.(MainActivity[=parentActivity] 위에 원하는 액티비티가 있도록 배스택을 형성한다. -> 뒤로 가기를 누르면 매인액티비로 연결된다.)
  • 액티비티 를 연결하는 버튼을 추가했던 거와 비슷하게 PendingIntent를 생성하여 알림등록을 한다. builder안에서 setContentIntent(pendingIntent)를 사용하여 연결할 수 있다.

6. 안드로이드 12(API 레벨 33)부터, 알림을 보내기 위한 권한 시스템 설정

  • 프라이버시 강화와 사용자 동의 없는 알림 전송을 방지하기 위해서 권한부여 대화상자를 표시해야한다.

  • POST_NOTIFICATIONS 권한을 앱의 매니페스트 파일에 명시적으로 추가해야 한다.

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
       package="com.example.myapp">
    
       <!-- API 33 이상을 위한 알림 권한 추가 -->
       <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
    
       ...
    </manifest>
  • 앱이 실행중일때에도 사용자에게 알림 권한을 요청 할 수 있다.

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
       if (!NotificationManagerCompat.from(this).areNotificationsEnabled()) {
           // 알림 권한이 없다면, 사용자에게 권한 요청
           val intent = Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS).apply {
               putExtra(Settings.EXTRA_APP_PACKAGE, packageName)
           }
           startActivity(intent)
       }
    }

✏새롭게 알게된 것과 느낀점

  • 개인과제 시간을 확보하기 위해서 프래그먼트, 다이얼로그, 알림에 관한 이론을 한꺼번에 들었다. 수업은 길지 않았는데, 정리하는데 꽤 시간이 들었다.
  • 프래그먼트는 실습과 함께 진행해서 이해도가 빨랐지만. 다이얼로그랑 알림은 그냥 바로 과제에 적용하는게 더 빠를 것 같아서 이론 위주로 정리했더니 솔직히 지금은 어지럽다.ㅎ
  • 다만, 찾아보니까 다이얼로그는 다이얼프레그먼트를 더 많이 쓴다는데.. 배우기는 일반 다이얼로그 클래스를 배워서.. 다이얼프레그먼트에 일반 다이얼로그 클래스를 쉽게 적용할 수 있다고 하는데.. 과제에서 한번 써먹어 봐야겠다.
  • 알림이 의외로 복잡한게 많았다. api33이상은 권한도 줘야하고.. 근데 내가 만드는 과제 api는 보통 31 정도를 기준으로 잡아서 사용자권한..은 시간이 남으면 하고 싶다. 근데 필수로 하라고 빨간줄 뜨면 열심히 해야겠지..ㅎ
  • 프래그먼트는 의외로 실습하면서 따라가는데 딱히 어려움이 없었다. 부디 과제에도 이렇게만 되기를 바란다.
  • 알림은 솔직히.. 관심 없었지만, 다이얼로그와 프레그먼트는 저번 과제 때 부터 아.. 이거 쓰는게 더 편할것같은데.. 라고 생각한 것들이라서 .. 막상.. 해보면 편..한지는 모를것 같긴하지만 유용하다는 것은 틀림 없다고 생각한다. 내일..부터는 본격적으로 과제를 수행하면서 만나는 트러블슈팅을 통해 실무적으로 적응하는 시간을 가져야겠다. 그떄 더 정리하면 오늘 보다 나아질거라고 믿는다.!
profile
파이팅!

0개의 댓글