개발을 하다 보면 화면을 이동하면서 데이터를 전달해야하는 경우가 굉장히 많다!
오늘은 이 부분에 대해 다뤄보려고 한다.
데이터 전달 완⭐️벽⭐️정⭐️복 가보자고👊🏻 렛츠긔~!
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가지를 사용할 수 있다.
이 경우에는 데이터 클래스가 아니라, 변수 몇 개를 그냥 넘겨주는 경우에 사용하기 좋다.
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)
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")!!
)
}
}
데이터를 클래스 형태로 넘기고 싶다면, Gson을 이용해 Json 형태로 변환한 수 넘길 수 있다. Gson은 쉽게 말해 Data Class처럼 kt 파일 내에서 사용할 수 있는 형태인데, 이를 바로 넘기는 건 불가능해서 넘길 수 있는 형태인 Json으로 변환한 다음 넘겨주어야 한다. 받는 쪽에서는 그래서 이 Json 형태로 넘어온 데이터를 받아서 Gson으로 변환한 다음 인스턴스에 할당해주어야 한다.
implementation 'com.google.code.gson:gson:2.9.0'
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)
}
}
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 간에 쉽게 주고받을 수 있다.
Bundle를 통해서 데이터를 전달할 수 있다. 이번에는 일반 데이터와 data class를 한 번에 넘겨보자.
우선, Album.kt
라는 데이터 클래스의 내용을 살펴보자.
data class Album(
var title: String? = "",
var singer: String? = "",
var coverImg: Int? = null,
var song: ArrayList<Song>? = null // 수록곡
)
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()
}
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 데이터를 넘기고, 뷰를 초기화해줄 수도 있다.
이 경우도 Activity -> Activity와 마찬가지로 Intent를 활용하면 된다.
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)
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
}
}
알다시피, 보통 Activity안에는 Fragment가 위치할 수 있다. 같은 Activity 안에 위치한 경우라면 Fragment -> Fragment
로 데이터를 전달하면 될 것이고, Activity가 바로 쓰이는 경우라면 Fragment -> Activity
데이터 전달을 참고하면 될 것이다.
하지만 다른 Activity의 Fragment로 데이터를 전달하는 경우라면, 우선 Activity -> Activity 데이터 전달 다음에 Activity -> Fragment 데이터 전달이 이루어져야 할 것이다.
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)
}
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
}
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 방법을 참고해도 된다.
spf에도 데이터를 저장할 수 있다.
spf에서 저장한 데이터를 다른 화면에서 사용하고자 할 때는 생명주기를 잘 고려해주어야 하겠지만, 어쨌든 데이터를 저장하고 꺼내쓰는 하나의 방법이 될 수 있다.
이 경우에는 앱 내부에 그냥 spf를 저장하는 것이므로, Activity냐, Fragment냐가 크게 차이가 없다.
이 경우, 아래 자료를 함께 참고하면 좋을 것 같다.
👉🏻 [Android/Kotlin] 리사이클러뷰 아이템 클릭 시 데이터 넘기기 (SharedPreferences를 Json 포맷으로 관리)
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 등 다양한 데이터 형식을 사용할 수 있다.
이 방법은 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 형태로 만들어주는 코드이다.
오늘은 안드로이드 개발에서 중요한!
화면 간의 데이터를 주고받는 것, 그리고 데이터를 저장하고 불러오는 방법에 대해서 알아보았다.
상황에 따라 데이터 전달을 다르게 해줘야 하니, 유념해서 사용하자.