>알고리즘 문제
1. 거리두기 확인하기
1)문제
문제 설명
개발자를 희망하는 죠르디가 카카오에 면접을 보러 왔습니다.
코로나 바이러스 감염 예방을 위해 응시자들은 거리를 둬서 대기를 해야하는데 개발 직군 면접인 만큼
아래와 같은 규칙으로 대기실에 거리를 두고 앉도록 안내하고 있습니다.
- 대기실은 5개이며, 각 대기실은 5x5 크기입니다.
- 거리두기를 위하여 응시자들 끼리는 맨해튼 거리1가 2 이하로 앉지 말아 주세요.
- 단 응시자가 앉아있는 자리 사이가 파티션으로 막혀 있을 경우에는 허용합니다.
예를 들어,
위 그림처럼 자리 사이에 파티션이 존재한다면 맨해튼 거리가 2여도 거리두기를 지킨 것입니다. | 위 그림처럼 파티션을 사이에 두고 앉은 경우도 거리두기를 지킨 것입니다. | 위 그림처럼 자리 사이가 맨해튼 거리 2이고 사이에 빈 테이블이 있는 경우는 거리두기를 지키지 않은 것입니다. |
응시자가 앉아있는 자리(P)를 의미합니다. | 빈 테이블(O)을 의미합니다. | 파티션(X)을 의미합니다. |
5개의 대기실을 본 죠르디는 각 대기실에서 응시자들이 거리두기를 잘 기키고 있는지 알고 싶어졌습니다. 자리에 앉아있는 응시자들의 정보와 대기실 구조를 대기실별로 담은 2차원 문자열 배열 places가 매개변수로 주어집니다. 각 대기실별로 거리두기를 지키고 있으면 1을, 한 명이라도 지키지 않고 있으면 0을 배열에 담아 return 하도록 solution 함수를 완성해 주세요.
제한사항
- places의 행 길이(대기실 개수) = 5
- places의 각 행은 하나의 대기실 구조를 나타냅니다.
- places의 열 길이(대기실 세로 길이) = 5
- places의 원소는 P,O,X로 이루어진 문자열입니다.
- places 원소의 길이(대기실 가로 길이) = 5
- P는 응시자가 앉아있는 자리를 의미합니다.
- O는 빈 테이블을 의미합니다.
- X는 파티션을 의미합니다.
- 입력으로 주어지는 5개 대기실의 크기는 모두 5x5 입니다.
- return 값 형식
- 1차원 정수 배열에 5개의 원소를 담아서 return 합니다.
- places에 담겨 있는 5개 대기실의 순서대로, 거리두기 준수 여부를 차례대로 배열에 담습니다.
- 각 대기실 별로 모든 응시자가 거리두기를 지키고 있으면 1을, 한 명이라도 지키지 않고 있으면 0을 담습니다.
2)솔루션
class Solution {
fun solution(places: Array<Array<String>>): IntArray {
var answer: IntArray = intArrayOf()
for(i in places){
answer+=room(i)
}
return answer
}
fun room(place:Array<String>):Int{
for(i in place.indices){
for((j,v) in place[i].withIndex()){
if(v=='P'&&!check(i,j,place)) return 0
}
}
return 1
}
fun check(i:Int, j:Int, place:Array<String>):Boolean{
val dx=intArrayOf(0,0,1,-1)
val dy=intArrayOf(1,-1,0,0)
val visited=HashSet<Pair<Int,Int>>()
val q=ArrayDeque<Triple<Int,Int,Int>>() //x,y,dist
q.addLast(Triple(i,j,2))
visited+=Pair(i,j)
while(q.isNotEmpty()){
val (x,y,dist) = q.removeFirst()
if(place[x][y]=='P'&&dist<2) return false
if(dist==0) continue
for(k in 0..3){
val nx=x+dx[k]
val ny=y+dy[k]
if(nx in 0..4&&ny in 0..4&&!visited.contains(Pair(nx,ny))&&place[x][y]!='X'){
visited+=Pair(nx,ny)
q.addLast(Triple(nx,ny,dist-1))
}
}
}
return true
}
}
-BFS이용
2. 숫자 카드 나누기
1)문제
문제 설명
철수와 영희는 선생님으로부터 숫자가 하나씩 적힌 카드들을 절반씩 나눠서 가진 후, 다음 두 조건 중 하나를 만족하는 가장 큰 양의 정수 a의 값을 구하려고 합니다.
- 철수가 가진 카드들에 적힌 모든 숫자를 나눌 수 있고 영희가 가진 카드들에 적힌 모든 숫자들 중 하나도 나눌 수 없는 양의 정수 a
- 영희가 가진 카드들에 적힌 모든 숫자를 나눌 수 있고, 철수가 가진 카드들에 적힌 모든 숫자들 중 하나도 나눌 수 없는 양의 정수 a
예를 들어, 카드들에 10, 5, 20, 17이 적혀 있는 경우에 대해 생각해 봅시다. 만약, 철수가 [10, 17]이 적힌 카드를 갖고, 영희가 [5, 20]이 적힌 카드를 갖는다면 두 조건 중 하나를 만족하는 양의 정수 a는 존재하지 않습니다. 하지만, 철수가 [10, 20]이 적힌 카드를 갖고, 영희가 [5, 17]이 적힌 카드를 갖는다면, 철수가 가진 카드들의 숫자는 모두 10으로 나눌 수 있고, 영희가 가진 카드들의 숫자는 모두 10으로 나눌 수 없습니다. 따라서 철수와 영희는 각각 [10, 20]이 적힌 카드, [5, 17]이 적힌 카드로 나눠 가졌다면 조건에 해당하는 양의 정수 a는 10이 됩니다.
철수가 가진 카드에 적힌 숫자들을 나타내는 정수 배열 arrayA와 영희가 가진 카드에 적힌 숫자들을 나타내는 정수 배열 arrayB가 주어졌을 때, 주어진 조건을 만족하는 가장 큰 양의 정수 a를 return하도록 solution 함수를 완성해 주세요. 만약, 조건을 만족하는 a가 없다면, 0을 return 해 주세요.
제한사항
제한사항
- 1 ≤ arrayA의 길이 = arrayB의 길이 ≤ 500,000
- 1 ≤ arrayA의 원소, arrayB의 원소 ≤ 100,000,000
- arrayA와 arrayB에는 중복된 원소가 있을 수 있습니다.
2)솔루션
class Solution {
fun solution(arrayA: IntArray, arrayB: IntArray): Int {
return maxOf(find(arrayA,arrayB),find(arrayB,arrayA))
}
fun find(array1:IntArray, array2:IntArray):Int{
val min=array1.minOrNull() as Int
val fac=HashSet<Int>()
for(i in 1..min){
var flag=true
for(j in array1){
if(j%i!=0){
flag=false
break
}
}
if(flag) fac+=i
}
val removed=HashSet<Int>()
for(i in fac){
for(j in array2){
if(j%i==0){
removed+=i
break
}
}
}
fac.removeAll(removed)
return fac.maxOrNull()?: 0
}
}
3. 멀쩡한 사각형
1)문제
문제 설명
가로 길이가 Wcm, 세로 길이가 Hcm인 직사각형 종이가 있습니다. 종이에는 가로, 세로 방향과 평행하게 격자 형태로 선이 그어져 있으며, 모든 격자칸은 1cm x 1cm 크기입니다. 이 종이를 격자 선을 따라 1cm × 1cm의 정사각형으로 잘라 사용할 예정이었는데, 누군가가 이 종이를 대각선 꼭지점 2개를 잇는 방향으로 잘라 놓았습니다. 그러므로 현재 직사각형 종이는 크기가 같은 직각삼각형 2개로 나누어진 상태입니다. 새로운 종이를 구할 수 없는 상태이기 때문에, 이 종이에서 원래 종이의 가로, 세로 방향과 평행하게 1cm × 1cm로 잘라 사용할 수 있는 만큼만 사용하기로 하였습니다.
가로의 길이 W와 세로의 길이 H가 주어질 때, 사용할 수 있는 정사각형의 개수를 구하는 solution 함수를 완성해 주세요.
- W, H : 1억 이하의 자연수
입출력 예
WHresult8 | 12 | 80 |
2)솔루션
class Solution {
fun solution(w: Int, h: Int): Long {
var answer: Long = 0
for(i in 0..w-1){
val prev=(h.toLong()*i.toLong())/w.toLong()
val end=ceil(i.toLong()+1L,w.toLong(),h.toLong())
answer+=end-prev
}
return w.toLong()*h.toLong()-answer
}
fun ceil(x:Long,w:Long,h:Long):Long{
var result=(h*x)/w
if((h*x)%w!=0L) result++
return result
}
}
-각 x범위 구간 길이 1마다 y좌표범위를 구한다,
>특강 코드 정리
@MockUp Data: 실제 데이터를 가져오기 힘들은 경우 대체되어 사용하는 더미 데이터를 의미함
1. 코드
1)data / UserEntity
@Parcelize
data class UserEntity(
val name : String,
val age : Int,
val cardNumber : String,
val cardPeriod : String,
val balance : BigDecimal,
val cardType : MultiViewEnum
) : Parcelable
-유저 정보를 담는 데이터 클래스
-Bundle에 담기위해서 직렬화(@Parcelize) 시킴
-@BigDecimal: Float나 Double처럼 자동 올림처리없이 정확한 값을 표현- 주로 금융권에서 사용
-Cache데이터는 보통 ~Entity로 이름 지음
2)data / userList
fun userList(): List<UserEntity> {
return listOf(
UserEntity(
"ggilggil monster yongchan",
24,
"2323-4343-5555",
"02/11",
332434.toBigDecimal(),
MultiViewEnum.BLUE
),
UserEntity(
"jammin-school jiwon",
18,
"12323-4343-5555",
"02/11",
12324.toBigDecimal(),
MultiViewEnum.ORANGE
),
UserEntity(
"psychopath kangjin",
51,
"768768-4343-5555",
"04/11",
64455.toBigDecimal(),
MultiViewEnum.LIGHTBLUE
),
UserEntity(
"heeyoung tutor",
21,
"34768-4343-5555",
"05/11",
11233.toBigDecimal(),
MultiViewEnum.BLUE
)
)
}
-유저 정보를 담은 함수
-위와 같이 클래스 없이 전역으로 함수나 변수 선언 가능
3)data / CacheDataSource
//TODO : 방법 1) synchronized Singleton Pattern
class CacheDataSource {
companion object {
@Volatile
private var INSTANCE: CacheDataSource? = null
fun getCacheDataSource(): CacheDataSource {
return synchronized(this) {
val newInstance = INSTANCE ?: CacheDataSource()
INSTANCE = newInstance
newInstance
}
}
}
fun getUserList(): List<UserEntity> {
return userList()
}
}
//TODO : 방법 2) object
/*
object CacheDataSource {
fun getUserList() : List<UserEntity> {
return userList()
}
}
*/
-그냥 getUserList()를 불러오면 그때마다 클래스 객체를 생성해 줘야 하므로 매번 새로운 메모리 영역을 차지함
=>싱글톤 패턴으로 객체가 한 메모리에 저장되어 있도록 함
-this는 companion object를 가리키며 synchronized는 보통 특정 클래스의 인스턴스나 Companion object를 인자로 받는걸 권장함
=>원래는 KClass(클래스에 대한 메타데이터를 담은 것)인 CacheDataSource::class로 했으나 this로 바꿈
-또한 캐싱하지말고 메모리에 즉시 반영시키기 위해 @Volatile 어노테이션 추가
-데이터 소스 객체 생성시 있으면 기존 객체를 불러오고 없으면 새로 객체를 만들어 저장 후 반환
4)presentation / UserRepository
interface UserRepository {
fun getUserList() : List<UserEntity>
}
-레포지토리 구현을 위한 인터페이스
-데이터소스와 상관없이 데이터를 List<UserEntity>로 반환하는 구조
-데이터소스를 캡슐화 시킴
5)data / GetUserRepositoryImple
class GetUserRepositoryImpl(
private val cacheDataSource: CacheDataSource
) : UserRepository {
override fun getUserList(): List<UserEntity> {
return cacheDataSource.getUserList()
}
}
-UserRepository를 구현
-DataSource인 CacheDataSource를 받아 값을 반환하는 메소드 오버라이드
6)presentation / MultiViewEnum
enum class MultiViewEnum (val viewType: Int) {
BLUE(0),
LIGHTBLUE(1),
ORANGE(2),
UNKNOWN(-1)
}
-viewType에 따른 카드색을 구분해주기 위한 Enum class
@Enum Class
<1> 일반 선언 형태
enum class Fruit{
GRAPE,
APPLE,
ORANGE,
MANGO
}
val fruit = Fruit.GRAPE
fruit.name // "GRAPE"
fruit.ordinal // 0
-name: 인스턴스의 이름
-ordinal: 인스턴스의 순서
<2> 생성자 정의
enum class Fruit(val price:Int,val tag:String){
GRAPE(3000,"grape"),
APPLE(2000,"apple"),
ORANGE(4000,"orange"),
MANGO(10000,"mango")
}
val fruit = Fruit.GRAPE
fruit.price // 3000
fruit.tag // "grape"
-생성자 정의시 각 인스턴스에도 동일한 생성자로 값을 넣어줘야함.
-생성자로 들어간 프로퍼티로 접근 가능
<3> 함수 및 프로퍼티 추가
enum class Fruit(val price:Int,val tag:String){
GRAPE(3000,"grape"),
APPLE(2000,"apple"),
ORANGE(4000,"orange"),
MANGO(10000,"mango");
fun printName():String = "fruit name is $name"
val tenPiecePrice
get() = price*10
}
val fruit = Fruit.GRAPE
fruit.printName() // "fruit name is GRAPE"
fruit.tenPiecePrice // 30000
-함수 및 프로퍼티 추가시 인스턴스 마지막에 ; 를 붙여줘야 한다.
-함수나 프로퍼티에 사용되는 것은 생성자에 정의된 프로퍼티이며 사용시 해당 인스턴스에 맞게 반환된다.
<4> 추상 메소드와 인터페이스 정의
enum class Fruit(price:Int,tag:String){
GRAPE(3000,"grape"){
override fun printName():String = "fruit name is grape"
},
APPLE(2000,"apple"){
override fun printName():String = "fruit name is apple"
},
ORANGE(4000,"orange"){
override fun printName():String = "fruit name is orange"
},
MANGO(10000,"mango"){
override fun printName():String = "fruit name is mango"
};
abstract fun printName():String
}
interface Printable(){
fun printName():String
}
enum class Fruit(price:Int,tag:String):Printable{
GRAPE(3000,"grape"){
override fun printName():String = "fruit name is grape"
},
APPLE(2000,"apple"){
override fun printName():String = "fruit name is apple"
},
ORANGE(4000,"orange"){
override fun printName():String = "fruit name is orange"
},
MANGO(10000,"mango"){
override fun printName():String = "fruit name is mango"
};
}
-추상메소드의 경우 클래스 내부에 정의하여 각 인스턴스 마다 오버라이드 해준다.
-인터페이스의 경우 외부에 정의된 인터페이스를 상속해서 인스턴스마다 오버라이드 해주는 방식으로 구현
=>이때 enum클래스는 인터페이스는 상속받을 수 있나 일반클래스나 다른 enum class는 상속받을 수 없다.
<5> 그 외 메소드
-find{}: value와 일치하는 이름을 가진 enum인스턴스 반환
-entries: Enum클래스에 포함된 값들을 Array로 반환 =>모든 인스턴스를 Array 반환
7)presentation / EventBus
object EventBus {
private val _events = MutableSharedFlow<Bundle>()
val events = _events.asSharedFlow()
suspend fun produceEvent(event : Bundle) {
_events.emit(event)
}
}
-_events: mutable이므로 외부에서 함부로 접근할 수 없도록 private로 선언.
=>데이터가 변하는 경우 emit를 통해 구독자들에게 값을 뿌려준다
-events: 외부에서 접근 가능한 변수. .asSharedFlow()로 _events를 Immutable하게 받는 변수로 외부에서 collect로 구독할 수 있게 해준다.
-suspend fun produceEvent: suspend 이므로 코루틴 스코프에서 사용가능. 메소드 실행시 emit을 통해 collect로 값을 뿌려줌
8)presentation / MultiCardAdapter
class MultiCardAdapter(private val onClick: (UserEntity,Int) -> Unit) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
var userList = listOf<UserEntity>()
//TODO
//viewholder 생성
//ViewHolder에 연결된 view 생성, 초기화
//multi view type 처리
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
//multi view type을 구현하는 item layout 연결
//enum ordinal값 사용 보단 enum의 entries(enum의 list를 뽑아서 return)를 뽑아서 사용
//sealed class
val MuiltiViewType = MultiViewEnum.entries.find { it.viewType == viewType }
return when (MuiltiViewType) {
MultiViewEnum.BLUE -> {
val binding =
ItemBlueCardBinding.inflate(LayoutInflater.from(parent.context), parent, false)
BlueTypeViewHolder(binding)
}
MultiViewEnum.LIGHTBLUE -> {
val binding =
ItemLightBlueCardBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
LightBlueTypeViewHolder(binding)
}
MultiViewEnum.ORANGE -> {
val binding =
ItemOrangeCardBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
OrangeTypeViewHolder(binding)
}
else -> {
val binding =
ItemDefaultBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
UnknownViewHolder(binding)
}
}
}
override fun getItemCount(): Int {
return userList.size
}
//viewHolder와 data 바인딩
//클릭 이벤트 처리
//TODO
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val currentItem = userList[position]
when (holder) {
is BlueTypeViewHolder -> {
val blueHolder = holder as BlueTypeViewHolder
blueHolder.bind(currentItem)
holder.itemView.setOnClickListener {
onClick(currentItem,position)
}
}
is LightBlueTypeViewHolder -> {
val lightBlueHolder = holder as LightBlueTypeViewHolder
lightBlueHolder.bind(currentItem)
holder.itemView.setOnClickListener {
onClick(currentItem,position)
}
}
is OrangeTypeViewHolder -> {
val orangeHolder = holder as OrangeTypeViewHolder
orangeHolder.bind(currentItem)
holder.itemView.setOnClickListener {
onClick(currentItem,position)
}
}
}
}
//아이템의 위치(position)에 따라 어떤 뷰 타입을 가져야하는지 결정
//position 즉 아이템의 위치에 접근하여 아이템의 뷰타입 결정
override fun getItemViewType(position: Int): Int {
return userList[position].cardType.viewType
}
//item layout의 ui값 뿌려주기
class BlueTypeViewHolder(private val binding: ItemBlueCardBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(user: UserEntity) {
binding.apply {
tvUserName.text = user.name
tvCardNum.text = user.cardNumber
tvCardPeriod.text = user.cardPeriod
tvBalance.text = user.balance.toString()
}
}
}
class LightBlueTypeViewHolder(private val binding: ItemLightBlueCardBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(user: UserEntity) {
binding.apply {
tvUserName.text = user.name
tvCardNum.text = user.cardNumber
tvCardPeriod.text = user.cardPeriod
tvBalance.text = user.balance.toString()
}
}
}
class OrangeTypeViewHolder(private val binding: ItemOrangeCardBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(user: UserEntity) {
binding.apply {
tvUserName.text = user.name
tvCardNum.text = user.cardNumber
tvCardPeriod.text = user.cardPeriod
tvBalance.text = user.balance.toString()
}
}
}
//TODO
//Enum외의 data가 왔을 때(server or android 개발자) 대응
class UnknownViewHolder(
binding: ItemDefaultBinding
) : RecyclerView.ViewHolder(binding.root) {
fun bind() = Unit
}
}
-클릭시 이벤트 처리는 람다함수 파라미터로 사용
=>이후 어댑터 생성시 클릭이벤트 메소드를 인자로 받음
-onCreateViewHolder:
[1]viewType을 가지고 EnumClass 인스턴스에서 알맞는 값을 가져와 MultiViewType에 저장
@find{ ~ }: 컬렉션에서 조건에 맞는 마지막 원소나 없으면 null을 반환
[2]when()문을 가지고 알맞은 ViewHolder를 생성해 반환한다.
-onBindViewHolder:
[1]인자로 받은 holder를 가지고 when()문으로 타입을 체크: is ~
[2]뷰에 데이터를 bind시켜주고 클릭이벤트를 설정해준다.
-getItemViewType: position을 가지고 유저 리스트의 아이템에 접근해 viewType을 체크
-ViewHolder: EnumClass외의 데이터가 왔을때를 대비하여 UnKnownViewHolder까지 중첩클래스로 생성
@중첩클래스의 경우 중첩클래스를 감싸고 있는 외부 클래스에서는 바로 사용 가능
9)presentation / FirstDataFragment
class FirstDataFragment : Fragment() {
companion object {
fun newInstance() = FirstDataFragment()
}
private val userRepository = GetUserRepositoryImpl(CacheDataSource.getCacheDataSource())
private lateinit var userEntityList : List<UserEntity>
private var _binding: FragmentFirstBinding? = null
private val binding get() = _binding!!
private var selected = 0
private val multiCardAdapter: MultiCardAdapter by lazy {
MultiCardAdapter { user,position ->
adapterOnClick(user,position)
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentFirstBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
getUser()
initView()
}
private fun initView() = with(binding) {
buttonNext.setOnClickListener {
lifecycleScope.launch {
EventBus.produceEvent(makeBundleUserObject())
}
}
rvItem.adapter = multiCardAdapter
multiCardAdapter.userList = userEntityList
}
//TODO bundle로 object 넘기기
private fun makeBundleUserObject(): Bundle {
return Bundle().apply {
putParcelable("user", getUser())
}
}
private fun getUser(): UserEntity {
userEntityList = userRepository.getUserList()
//TODO : use Singleton Class Instance
return userRepository.getUserList().get(selected)
//TODO : use Object
//return CacheDataSource.getUserList()
}
private fun adapterOnClick(user: UserEntity, position:Int) {
Toast.makeText(requireContext(), "유저 이름 : " + user.name, Toast.LENGTH_SHORT).show()
selected=position
}
}
-userRepository: 레포지토리가 저장되는 변수
-userEntityList: 유저 데이터 정보가 담겨있는 리스트
-multiCardAdapter: 어댑터가 받는 람다함수를 이곳에 구현한 클릭 메소드를 이용해 인자를 전달
-onViewCreated에서 getUser()와 initView()실행
<1>getUser()
-Repository로 부터 유저 정보들을 받아옴
-추후에 다른 프래그먼트로 넘겨줄 데이터를 반환하는 역할도 함
<2>initView()
-버튼 클릭시 EventBus를 이용해 번들로 감싸진 유저데이터를 보냄
=>makeBundleUserObject로 getUser에서 받아온 유저정보를 번들로 감쌈
-어댑터를 리사이클러뷰와 연결시켜주고 어댑터에 유저 리스트를 넣는다.
10)presentation / SecondDataFragment
class SecondDataFragment : Fragment() {
companion object {
fun newInstance(bundle: Bundle): SecondDataFragment {
val secondDataFragment = SecondDataFragment()
secondDataFragment.arguments = bundle
return secondDataFragment
}
}
private var _binding: FragmentSecondBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentSecondBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initView()
}
private fun initView() = with(binding) {
val user = arguments?.getParcelable<UserEntity>("user")
txtTitle.text = user?.name
}
}
-companion object를 이용해 프래그먼트 생성시 데이터를 arguments로 받아오도록 함
-onViewCreated에서 initView()실행
-initView(): 프래그 먼트 생성시 받아온 bundle값이 저장된 argument에서 유저 정보를 받아와 UI에 보여줌
11)presentation / MainFragmentContainerActivity
class MainFragmentContainerActivity : AppCompatActivity() {
private val binding: ActivityMainFragmentContainerBinding by lazy {
ActivityMainFragmentContainerBinding.inflate(layoutInflater)
}
private lateinit var userBundle: Bundle
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
setFragment()
lifecycleScope.launch {
EventBus.events.collect { bundle ->
userBundle = bundle
}
}
}
private fun setFragment() {
supportFragmentManager.beginTransaction()
.replace(R.id.fragment_container_view, FirstDataFragment.newInstance())
.commit()
binding.tabs.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab?) {
when (tab?.text.toString()) {
"first" -> {
supportFragmentManager.beginTransaction()
.replace(R.id.fragment_container_view, FirstDataFragment.newInstance())
.commit()
}
"second" -> {
if (::userBundle.isInitialized) {
supportFragmentManager.beginTransaction()
.replace(
R.id.fragment_container_view,
SecondDataFragment.newInstance(userBundle)
)
.commit()
}
}
}
}
override fun onTabUnselected(tab: TabLayout.Tab?) {
}
override fun onTabReselected(tab: TabLayout.Tab?) {
}
})
}
}
-userBundle:나중에 프래그먼트로 넘겨줄 번들데이터
-EventBus를 collect로 구독해서 데이터가 보내져올때 받는다.
-setFragment()
<1>기본 프래그먼트 설정: 처음 화면에 보여질 프래그먼트를 먼저 설정해 놓는다.
<2>변수 초기화 여부 확인: ::userBundel.isInitialized로 초기화 여부를 확인한다.
@Kproperty
- KProperty 인터페이스는 다양한 서브클래스를 통해 다양한 형태의 프로퍼티를 나타냅니다.
- KProperty0, KProperty1, KProperty2는 각각 파라미터가 0개, 1개, 2개인 프로퍼티를 나타냅니다.
- KMutableProperty0, KMutableProperty1, KMutableProperty2는 각각 변경 가능한 프로퍼티를 나타냅니다.
<1>KProperty0<R>
-객체인스턴스 없이 접근 가능한 프로퍼티: 싱글톤이나 top-level에 있는 프로퍼티를 의미
-예시(top-level)
val topLevelProperty: String = "Hello"
fun main() {
val property: KProperty0<String> = ::topLevelProperty
println(property.get()) // 출력: Hello
}
<2>KProperty1<T,R>
-객체 인스턴스에 속하는 프로퍼티
-예시
class Person(val name: String)
fun main() {
val person = Person("Alice")
val nameProperty: KProperty1<Person, String> = Person::name
println(nameProperty.get(person)) // 출력: Alice
}
<3>KProperty2<T, U, R>
-map-like 구조의 데이터: 객체와 key역할을 해줄 값들이 들어
class Matrix {
operator fun get(row: Int, col: Int): Int {
// 단순 예제, 실제로는 값을 반환해야 함
return row * col
}
}
fun main() {
val matrix = Matrix()
val getMethod: KProperty2<Matrix, Int, Int, Int> = Matrix::get
println(getMethod.get(matrix, 2, 3)) // 출력: 6
}
<4>KMutableProperty: 가변 데이터인 경우를 의미
=>val 을 var바꿔주면 됨.
=>Kproperty2의 경우는 setter를 설정해두면 됨.
@::변수명.isInitailized
=>lateinit으로 선언된 변수에 대해 사용 가능한 메소드로 초기화 여부를 Boolean으로 반환한다.
>compose 1주차
1.Compose
1)Compose란
-안드로이드 UI를 선언적으로 구축하는 도구
-기존 View방식(XML + Kotlin)으로 발생하는 다양한 문제 해결을 위해 등장
2)VIew의 문제점
<1>UI개발에서 높은 복잡성
-복잡한 UI다룰시 관리가 어려움
-지나친 중첩계층구조는 성능저하 야기
-XML과 Kotlin 요소와 코트가 분리되어 있지만 둘의 밀접한 로직 결합은 가독성과 재사용성을 떨어트림
<2>상속으로 인한 문제
-UI요소들이 추가될때 재사용을 위해 UI요소 만들때 상속받도록 설계
-유지보수가 어려워짐 =>새로운 요구사항 발생시마다CustomView클래스를 수정해야함
3)대체제로
-Kotlin 파일에 선언적 UI작성
-상속대신 합성을 채택해 UI개발 복잡성을 낮추고 유지보수성 향상
2.선언형(Declarative) UI
-무엇을 보여줄 것인가를 기술
-세부 구현은 프레임워크나 시스템에 위임
-Composable 함수라는 독립적인 단위로 UI구성하여 직관적이고 재사용성 높음
1)ViewModel활용
-Compose의 선언형 접근 방식에서 위젯인 비교적 stateless상태(state가 없음)이며 setter와 getter를 노출하지 않음
=>기존 view에서 setText, getText같은 것을 할 필요가 없음
-위젯은 객체로 노출되지 않음. =>동일 구성 가능한 함수를 다른 인수로 호출해 UI업데이트
2)데이터 흐름: 뷰모델에서 말단의 위젯으로 흘러감
3)Event 흐름: 말단 위젯에서 이벤트가 발생하면 뷰모델까지 거슬로 올라감
@데이터와 이벤트는 컴포저블 및 계층 구조를 타고 이동함.
3. MVVM 디자인 패턴
1)구성
<1>Model:
-ViewModel에서 요청한 데이터를 처리, 반환
-로컬 DB(SQLite,Room), 네트워크 통신(Retrofit) 이용
<2>View(UI Layer): 보통 액티비티, 프래그먼트
<3>ViewModel
-사용자 Action,lifecycle에 따라 View에서 요청한 데이터, 비즈니스 로직 처리
-Model에 요청한 데이터를 받음