Fragment간의 데이터 전달, 알림 만드는 법 (2024-07-16)

김성진·2024년 7월 16일
0

알림

정의

  • 우리가 아는 그 알림 맞다.
  • 사용자가 알림을 터치하여 앱을 열거나, 간단한 작업을 하 수 있다.
  • 안드로이드 8.0버전부터 배지로도 표시 가능하다 (앱 아이콘 위에 숫자 뜨는 거)
  • 그래서 그 알림을 만들기 위해선 안드로이드 8.0 버전 이상부터는 채널을 만들어야 한다.

코드 작성

전체 코드

val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager

// 여기는 잘 모르겠지만 builder 생성하는 코드로 보인다.
val builder: NotificationCompat.Builder

// 여기서부터 채널 생성
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {      // 안드로이드 8.0 버전 이상인지 검사
    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_NTIFICATION)         // 알림이 오면 소리가 나는데 그 소리를 기본 소리로 설정
        
        // 그 소리에 대한 자세한 설정. 자세하게는 모르겠다.. ㅋㅋㅋ
        val audioAttributes = AudioAttributes.Builder()
            .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
            .setUsage(AudioAttributes.USAGE_ALARM)
            .build()
            
            // 아무튼 그렇게 만든 소리를 최종적으로 알림의 소리로 설정한다.
        setSound(uri, audioAttributes)
        enableVibration(true)                  // 진동 발생 설정. 지금은 true
    }
    
    // 체널 등록 및 builder 설정 부분인 것 같다.
    manager.createNotificationChannel(channel)
    builder = NotificationCompat.Builder(this, channelId)
    
} else {      // 8.0 버전 이하이면 여기를 실행

    builder = NotificationCompat.Builder(this)
    
}

// 그림 및 큰 사진에 대한 설정
val bitmap = BitmapFactory.decodeResource(resource, R.drawable.이미지 이름)       // 사진을 넣겠다고 하면 이 사진을 넣는다.
val intent = Intent(this, 액티비티이름::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.drawable.이미지이름)
    setWhen(System.currentTImeMillis())         // 알림 시간을 현재 시간으로 설정
    setContentTitle(타이틀)
    setContentText(내용)
    
    // 긴 텍스트 쓸 때 이 부분 쓴다.
    setStyle(NotificationCompat.BigTextStyle()
        .bigText("이것은 긴텍스트 샘플입니다. 아주 긴 텍스트를 쓸때는 여기다 하면 됩니다.이것은 긴텍스트 샘플입니다. 아주 긴 텍스트를 쓸때는 여기다 하면 됩니다.이것은 긴텍스트 샘플입니다. 아주 긴 텍스트를 쓸때는 여기다 하면 됩니다."))
    setLargeIcon(bitmap)
//  setStyle(NotificationCompat.BigPictureStyle()
//       .bigPicture(bitmap)
//       .bigLargeIcon(null))
    addAction(R.mipmap.ic_launcher, "Action", pendingIntent)     // 여기도 아직 공부가 필요하다.
}

// 알림을 최종적으로 실행하여 보여줌
    manager.notify(11, builder.build())
}

자! 전체 코드는 이렇다! 되게 복잡해 보이지만 전체적인 흐름만 파악하면 된다.


Fragment 데이터 전달

왜 배워야 하는가?

  • Fragment는 앱에 거의 필수적으로 들어가는 기능이고, 이 과정에서 Fragment끼리, 혹은 액티비티와 데이터를 주고받는다. 그렇기 때문에 이것을 잘 알아두어야 한다.

실습

전체 코드

  • MainActivity.kt
package com.example.fragmentexample

import android.os.Bundle
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.commit
import androidx.fragment.app.replace
import com.example.fragmentexample.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity(), FragmentDataListener {
    private val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContentView(binding.root)
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
            val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
            insets
        }
        
        binding.run {
            fragment1Btn.setOnClickListener {        // 1번 버튼 누르면
                val dataToSend = "텍스트"             // 이 string 타입 문자열을
                val fragment = FirstFragment.newInstance(dataToSend)      // FirstFragment의 newInstance에 넣는다.
                setFragment(fragment)     // 그리고 FirstFragment를 화면에 보인다.
            }
            
            // 여기는 1번 버튼 눌렀을 때와 버튼만 다르고 동작은 똑같으니 생략
            fragment2Btn.setOnClickListener {
                val dataToSend = "텍스트2"
                val fragment = SecondFragment.newInstance(dataToSend)
                setFragment(fragment)
            }
        }
        
        // 기본적으로 시작할 때 보일 Fragment는 FirstFragment
        set Fragment(FirstFragment())
}

// 이건 지난번에 했던 Fragment 화면에 보여주는 코드
private fun setFragment(frag: Fragment) {
    supportFragmentManager.commit {
        repalce(R.id.frameLayout, frag)      // 이제 알았다. 이건 frameLayout 안에 Fragment 넣겠다는 뜻이었다... ㅋㅋㅋ
        setReorderingAllowed(true)
        addToBackStack("")
    }
}

// interface를 실행하기 위한 코드
// Fragment에서 액티비티로 데이터 전달받아 토스트 메세지를 띄운다.
// 그게 맨 위에서 FragmentDataListener를 상속받은 이유이다.
override fun onDataReceived(data: String) {
    Toast.makeText(this, data, Toast.LENGTH_SHORT).show()
}
  • FirstFragment.kt
package com.example.fragmentexample

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.example.fragmentexample.databinding.FragmentFirstBinding

private const val ARG_PARAM1 = "param1"        // 여기는 익숙한 부분이다. 마치 인텐트 받아오듯이 상수 선언 파트

clas FirstFragment : Fragment() {                                 // Fragment 상속 받기.. 여기 진짜 중요하다!
    private var param1: String? = null          // 받아온 값 넣을 변수 선언. 클래스 밖에도 선언해 봤는데 결과는 정상적으로 나옴.
    private val binding by lazy { FragmentFirstbinding.inflate(layoutInflater) }  // 이건 뭐.. 익숙하니 생략
    
    // 여기는 받아온 값을 param1이라는 변수에 넣는 부분
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        arguments?.let {
            param1 = it.getString(ARG_PARAM1)
        }
    }
    
    // 여긴 여전히 뭔지 모르겠다.
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

		// 최종적으로 Fragment의 텍스트에 받아온 값을 넣는다.
        binding.tvFrag1Text.text = param1

		// Fragment -> Fragment
        // 이 부분은 뭐.. 똑같다... 설명 생략
        binding.btnGoFrag2.setOnClickListener {
            val dataToSend = "텍스트3"
            val fragment2 = SecondFragment.newInstance(dataToSend)

			// 여기는 데이터를 전달함과 동시에 fragment2를 화면에 보여달란 의미이다.
            requireActivity().supportFragmentManager.beginTransaction()
                .replace(R.id.frameLayout, fragment2)
                .addToBackStack(null)
                .commit()
        }
    }
    
    // MainActivity에서 string을 받아와서 아까 맨 위에 선언한 변수에 넣는다.
    // 위에 코드 보면 알다시피 클래스 및 다른 함수에서 써야 되니 전역변수로 선언한 것이다.
    // param1도 마찬가지.
    companion object {
        @JvmStatic
        fun newInstance(param1: String) = FirstFragment().apply {
            argments = Bundle().apply {
                putString(ARG_PARAM1, param1)
            }
        }
    }
}
  • SecondFragment.kt
package com.example.fragmentexample

import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.example.fragmentexample.databinding.FragmentSecondBinding

// 이건 뭐... 설명 생략
private const val ARG_PARAM1 = "param1"

class SecondFragment : Fragment() {    // 역시 Fragment를 상속받는다.
    private var listener: FragmentDataListener? = null        // interface 관련 변수
    private var param1: String? = null                        // 이것은 first와 똑같다.
    
    // view binding 관련 변수
    private var _binding: FragmentSecondBinding? = null
    private val binding get() = _binding!!
    
    // SecondFragment와 MainActivity 연결해서  Fragment에서 Activity로 데이터 전달
    override fun onAttach(context: Context) {
        super.onAttch(context)
        
        if (context is FragmentDataListener) {
        // MainActivity에서 context가 들어온다.
        // 그러면서 FragmentDataListener 구현 여부를 물어본다.
        // True이면 listener에 context 대입
            listener = context
        } else {
            throw RuntimeException("$context must implement fragmentDataListener")
        }
    }
    
    // 여기는 first에서 했던 Activity -> Fragment의 SecondFragment 버전
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        arguments?.let {
            param1 = it.getString(ARG_PARAM1)
        }
    }
    
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        _binding = FragmentSecondBinding.inflate(inflater, container, false)
        return binding.root
    }
    
    override fun onViewCreated(view: View, savedInstance: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        
        binding.tvFrag2Text.text = param1
        
        // Fragment -> Activity
        binding.btnSendActivity.setOnClickListener {
            val dataToSend = "텍스트4"
            // MainActivity의 context가 listener로 들어갔으니 listener에 보낼 데이터 싣는다.
            // interface를 호출해서 그걸 이용한다. 그럼 거기에 데이터가 들어간다.
            // 근데 그 interface를 MainActivity가 상속받고 있으므로 이 데이터는 MainActivity로 넘어간다.
            listener?.onDateReceived(dataToSend)
        }
    }
    
    // 여기도 설명 생략
    companion object {
        @JvmStatic
        fun newInstance(param1 : String) = SecondFragment().apply {
            arguments = Bundle().apply {
                putString(ARGPARAM1, param1)
            }
        }
    }
    
    override fun onDestoryView() {
        super.onDestroyView()
        
        _binding = null
        listener = null
    }
}
  • FragmentDataListener.kt -> interface 코드
interface FragmentDataListener {
	// interface 안의 onDataaReceived에 Fragment에서 Data를 넣으면 그걸 MainActivity가 상속 받아서 토스트 메세지를 띄운다.
    fun onataReceived(data: String)
}

profile
김성진의 개발 관련 내용 정리 블로그

0개의 댓글