[Android/Kotlin] 화면을 이동할 때 데이터를 전달하는 방법 총정리

코코아의 개발일지·2023년 11월 20일
1

Android-Kotlin

목록 보기
17/31
post-thumbnail

✍🏻 요구사항 분석

개발을 하다 보면 화면을 이동하면서 데이터를 전달해야하는 경우가 굉장히 많다!
오늘은 이 부분에 대해 다뤄보려고 한다.
데이터 전달 완⭐️벽⭐️정⭐️복 가보자고👊🏻 렛츠긔~!


💻 코드 작성

1️⃣ Activity -> Activity

Intent를 이용해서 데이터를 전달할 수 있다.
우선, Song이라는 데이터 클래스를 살펴보면 아래와 같이 구성되어있다.

data class Song(
    var title: String = "",
    var singer: String = "",
    var coverImg: Int? = null,
    var second: Int = 0,
    var playTime: Int = 0,
    var isPlaying: Boolean = false,
    var music: String = "" // 어떤 음악이 재생되고 있었는지 알려주는 변수
)

아래 데이터 클래스를 다음 Activity로 넘겨주는 방법에는 크게 2가지를 사용할 수 있다.

1. 일반적인 경우 (하나하나씩 넘겨주는 경우)

이 경우에는 데이터 클래스가 아니라, 변수 몇 개를 그냥 넘겨주는 경우에 사용하기 좋다.

  • 보내는 쪽 (MainActivity.kt)
private var song: Song = Song()

private fun moveToSongActivity() {
        binding.mainPlayerCl.setOnClickListener {
            val intent = Intent(this, SongActivity::class.java)
            intent.putExtra("title", song.title)
            intent.putExtra("singer", song.singer)
            intent.putExtra("coverImg", song.coverImg)
            intent.putExtra("second", song.second)
            intent.putExtra("playTime", song.playTime)
            intent.putExtra("isPlaying", song.isPlaying)
            intent.putExtra("music", song.music)
            // intent를 가지고 화면 이동
            startActivity(intent)
        }
    }

이 부분은 apply{}를 활용해 코드를 조금 더 간결하게 만들 수도 있다. 아래는 방금 작성한 코드랑 같은 동작을 한다.

val intent = Intent(this, SongActivity::class.java)
intent.apply {
	putExtra("title", song.title)
	putExtra("singer", song.singer)
	putExtra("coverImg", song.coverImg)
	putExtra("second", song.second)
	putExtra("playTime", song.playTime)
	putExtra("isPlaying", song.isPlaying)
	putExtra("music", song.music)
}
// intent를 가지고 화면 이동
startActivity(intent)
  • 받는 쪽 (SongActivity.kt)
lateinit var song: Song

 private fun initSong() {
        if (intent.hasExtra("title") && intent.hasExtra("singer")) {
            song = Song(
                intent.getStringExtra("title")!!,
                intent.getStringExtra("singer")!!,
                intent.getIntExtra("coverImg", 0),
                intent.getIntExtra("second", 0),
                intent.getIntExtra("playTime", 60),
                intent.getBooleanExtra("isPlaying", false),
                intent.getStringExtra("music")!!
            )
        }
    }

2. gson을 활용하는 경우

데이터를 클래스 형태로 넘기고 싶다면, Gson을 이용해 Json 형태로 변환한 수 넘길 수 있다. Gson은 쉽게 말해 Data Class처럼 kt 파일 내에서 사용할 수 있는 형태인데, 이를 바로 넘기는 건 불가능해서 넘길 수 있는 형태인 Json으로 변환한 다음 넘겨주어야 한다. 받는 쪽에서는 그래서 이 Json 형태로 넘어온 데이터를 받아서 Gson으로 변환한 다음 인스턴스에 할당해주어야 한다.

  • 의존성 추가 (build.gradle)
    먼저, Gson을 사용하고 싶다면 모듈 gradle에 의존성을 추가해 주어야 한다.
implementation 'com.google.code.gson:gson:2.9.0'

  • 보내는 쪽 (MainActivity.kt)
private fun moveToSongActivity() {
		binding.mainPlayerCl.setOnClickListener {
            val intent = Intent(this, SongActivity::class.java)

            val gson = Gson()
            val songJson = gson.toJson(song)
            intent.putExtra("song", songJson)
            // intent를 가지고 화면 이동
            startActivity(intent)
        }
}
  • 받는 쪽 (SongActivity.kt)
lateinit var song: Song
private var gson: Gson = Gson()

 private fun initSong() {
        // intent가 넘어왔는지 확인
        intent.getStringExtra("song")?.let { songJson ->
        	// 넘어왔다면 song 인스턴스에 gson 형태로 받아온 데이터를 넣어줌
            song = gson.fromJson(songJson, Song::class.java)
            Log.e("Song", song.toString()) // 로그 확인
        }
    }

이렇게 사용하면 data class 형태의 데이터를 Activity 간에 쉽게 주고받을 수 있다.



2️⃣ Fragent -> Fragment

Bundle를 통해서 데이터를 전달할 수 있다. 이번에는 일반 데이터와 data class를 한 번에 넘겨보자.
우선, Album.kt라는 데이터 클래스의 내용을 살펴보자.

data class Album(
    var title: String? = "",
    var singer: String? = "",
    var coverImg: Int? = null,
    var song: ArrayList<Song>? = null // 수록곡
)

  • 보내는 쪽 (HomeFragment.kt)
    gson을 사용하면 된다.
    그 다음에는 화면을 이동하면서 Json 형태의 데이터를 Bundle에 담아 넘기면 된다.
private fun moveToAlbumFragment(album: Album) {
        Log.d("HomeFragment", "album: $album")
        (context as MainActivity).supportFragmentManager.beginTransaction()
            .add(R.id.main_frm, AlbumFragment().apply {
                arguments = Bundle().apply {
                    val gson = Gson()
                    val albumJson = gson.toJson(album)
                    putString("album", albumJson) // data class 데이터
					putBoolean("isRoomData", isRoomDB) // 일반 데이터
                }
            })
            .addToBackStack(null) // 백 스택에 트랜잭션을 추가
            .commitAllowingStateLoss()
    }
  • 받는 쪽 (AlbumFragment.kt)
private var gson: Gson = Gson()

private fun receiveHomeData() {
        /* argument에서 데이터를 꺼내기*/
        // data class 데이터
        val albumJson = arguments?.getString("album")
        val album = gson.fromJson(albumJson, Album::class.java)
        // 일반 데이터
         val isRoomData = arguments?.getBoolean("isRoomData", false)
        // 뷰 랜더링
        setInit(album)
    }

argument에서 데이터를 꺼내서 album 객체에 넣을 수 있다. 이를 통해 setInit() 함수의 인자로 album 데이터를 넘기고, 뷰를 초기화해줄 수도 있다.


3️⃣ Fragment -> Activity

이 경우도 Activity -> Activity와 마찬가지로 Intent를 활용하면 된다.

  • 보내는 쪽 (NoticeFragment.kt)
val intent = Intent(activity, NoticeDetailActivity::class.java)

// 데이터 넣기
intent.apply {
	this.putExtra("noticeIdx", notice.id)
	this.putExtra("title", notice.title)
	this.putExtra("date", notice.date.substring(0 until 10))
}
// 화면 이동
startActivity(intent)
  • 받는 쪽 (NoticeDetailActivity.kt)
override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // 받아온 noticeIdx
        val noticeIdx = intent.getIntExtra("noticeIdx", 0)
        Log.d("받아온 noticeIdx", noticeIdx.toString())

        setInit()
    }
    
private fun setInit() {
        // MyNoticeFragment에서 넘어온 데이터
        val title = intent.getStringExtra("title")
        val date = intent.getStringExtra("date")

        // 값 넣어주기
        with(binding) {
            noticeDetailTitleTv.text = title
            noticeDetailDateTv.text = date
        }
    }



4️⃣ Activity -> (다른 Activity의) Fragment

알다시피, 보통 Activity안에는 Fragment가 위치할 수 있다. 같은 Activity 안에 위치한 경우라면 Fragment -> Fragment로 데이터를 전달하면 될 것이고, Activity가 바로 쓰이는 경우라면 Fragment -> Activity 데이터 전달을 참고하면 될 것이다.

하지만 다른 Activity의 Fragment로 데이터를 전달하는 경우라면, 우선 Activity -> Activity 데이터 전달 다음에 Activity -> Fragment 데이터 전달이 이루어져야 할 것이다.


LoginActivity에서 로그인 한 유저의 데이터를 MainActivity의 ProfileFragment로 전달하는 과정을 생각해 보자.
  • 넘기는 쪽 (LoginActivity.kt)
private fun moveToMainActivity(user: LoginUser) {

        // 다음 화면으로 넘길 데이터
        val intent = Intent(this@LoginActivity, MainActivity::class.java)
        val bundle = Bundle().apply {
            val gson = Gson()
            val userJson = gson.toJson(user)
            putString("user", userJson)
        }
        intent.putExtras(bundle)
        Log.d("LoginActivity", "넘기기: $user")
        // 화면 이동
        startActivity(intent)
    }

  • 받고, 다시 넘기는 쪽 (MainActivity.kt)
override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        // 가장 처음에 표시할 Fragment 설정 -> ProfileFragment
        binding.mainBottomNavi.selectedItemId = R.id.profile
        supportFragmentManager.beginTransaction()
            .replace(R.id.main_frm, passLoginData(ProfileFragment()))
            .commitAllowingStateLoss()
        // 바텀네비 아이템 클릭 이벤트 정의
        setBottomNavi()
    }
    
private fun passLoginData(fragment: Fragment): Fragment {

        // LoginActivity로부터 로그인한 유저 데이터 받기
        val loginData = intent.getStringExtra("user")

        Log.d("MainActivity", "받기: $loginData")

        // ProfileFragment에 다시 데이터 전달
        val bundle = Bundle()
        bundle.putString("user", loginData)
        fragment.arguments = bundle

        return fragment
    }
    
  • 받는 쪽 (ProfileFragment.kt)
private fun setInit() {

        val gson = Gson()
        // argument에서 데이터를 꺼내기
        val userJson = arguments?.getString("user")

        // 로그인한 유저 데이터 받기
        if (userJson != null) {
            val user = gson.fromJson(userJson, LoginUser::class.java)
            // 뷰에 랜더링
            initUserInfo(user)
        }
    }

이 과정을 거치면 다른 Activity의 Fragment에도 데이터를 넘길 수 있다! 물론, 이 과정이 너무 복잡하게 느껴진다면 아래 번외의 sharedPreferences 방법을 참고해도 된다.



(번외) sharedPreferences에 저장하기

spf에도 데이터를 저장할 수 있다.
spf에서 저장한 데이터를 다른 화면에서 사용하고자 할 때는 생명주기를 잘 고려해주어야 하겠지만, 어쨌든 데이터를 저장하고 꺼내쓰는 하나의 방법이 될 수 있다.
이 경우에는 앱 내부에 그냥 spf를 저장하는 것이므로, Activity냐, Fragment냐가 크게 차이가 없다.
이 경우, 아래 자료를 함께 참고하면 좋을 것 같다.
👉🏻 [Android/Kotlin] 리사이클러뷰 아이템 클릭 시 데이터 넘기기 (SharedPreferences를 Json 포맷으로 관리)

1. 일반적인 경우 (하나하나씩 저장하는 경우)

  • 저장하는 쪽
private fun savePlayingData() {
        songs[nowPos].second = ((binding.songPlayProgressSb.progress * songs[nowPos].playTime)/100)/1000 // 재생 시간을 초 단위로 변환

        // 어플이 종료해도 데이터가 남아있을 수 있도록 내부 저장소에 저장
        val sharedPreferences = getSharedPreferences("song", MODE_PRIVATE)
        val editor = sharedPreferences.edit() // 에디터
        editor.putInt("songId", songs[nowPos].id)
        editor.apply()
    }

그냥 값을 저장하려면 editor.putInt()식으로 활용할 수 있다. Int 말고도 String, Boolean 등 다양한 타입을 저장할 수 있으니까 이 부분은 자유롭게 활용하면 된다.
아래처럼 여러 값을 간편하게 저장할 수도 있다.

// 어플이 종료해도 데이터가 남아있을 수 있도록 마지막으로 재생한 곡의 id를 내부 저장소에 저장
        val sharedPreferences = getSharedPreferences("song", MODE_PRIVATE)
        val editor = sharedPreferences.edit() // 에디터
        // 데이터 넣기
		editor
            .putInt("songId", songs[nowPos].id)
            .putString("title", songs[nowPos].title)
            .putBoolean("isPlaying", songs[nowPos].isPlaying)
            .apply() // 반영

.apply()를 뒤에 꼭 써주어야 변경사항이 반영된다. edit()를 사용한 후에는 반드시 뒤에 .apply()를 써주어야 한다.

  • 불러오는 쪽
lateinit var song: Song
private var gson: Gson = Gson()

override fun onStart() {
        super.onStart()
        // spf에 저장된 songId를 가져옴
        val spf = getSharedPreferences("song", MODE_PRIVATE)
        val songId = spf.getInt("songId", 0)

        // 미니플레이어에 데이터 반영
        setMiniPlayer(song)
    }

이런 식으로 Int 형태의 데이터도 가져올 수 있다. Int 말고도 String, Boolean 등 다양한 데이터 형식을 사용할 수 있다.

2. gson을 활용하는 경우

이 방법은 Json 포맷으로 저장하고, 불러올 때 gson으로 바꿔서 사용하는 예시이다.

  • 저장하는 쪽
lateinit var song: Song
private var gson: Gson = Gson()

private fun savePlayingData() {
	// 재생 시간을 초 단위로 변환
	song.second = ((binding.songPlayProgressSb.progress * song.playTime)/100)/1000 

	// 어플이 종료해도 데이터가 남아있을 수 있도록 내부 저장소에 저장
	val sharedPreferences = getSharedPreferences("song", MODE_PRIVATE)
	val editor = sharedPreferences.edit() // 에디터
	val songJson = gson.toJson(song)
	editor.putString("songData", songJson)
	editor.apply()
}
  • 불러오는 쪽
lateinit var song: Song
private var gson: Gson = Gson()

override fun onStart() {
        super.onStart()
        val sharedPreferences = getSharedPreferences("song", MODE_PRIVATE)
        val songJson = sharedPreferences.getString("songData", null)

        song = if (songJson == null) { // 데이터가 존재하지 않으면 Song 데이터를 직접 넣어줌
            Song("라일락", "아이유(IU)", R.drawable.img_album_exp2,0, 60, false, "music_lilac")
        } else { // 존재하면 저장된 데이터를 넣어줌
            gson.fromJson(songJson, Song::class.java)
        }

        // 미니플레이어에 데이터 반영
        setMiniPlayer(song)
    }

Json 포맷으로 저장된 데이터를 불러와서, kt 파일에서 사용할 수 있는 Gson 형태로 만들어주는 코드이다.



🤗 마치며

오늘은 안드로이드 개발에서 중요한!
화면 간의 데이터를 주고받는 것, 그리고 데이터를 저장하고 불러오는 방법에 대해서 알아보았다.
상황에 따라 데이터 전달을 다르게 해줘야 하니, 유념해서 사용하자.

profile
우당탕탕 성장하는 개발자

0개의 댓글