본문 바로가기

전체 글140

Compose UI 컴포넌트 설계 Compose UI 컴포넌트 설계의 중요성안드로이드 Compose UI 설계는 단순히 화면을 구성하는 것을 넘어 재사용성과 테스트 용이성을 높이고, 잦은 변경 사항에도 유연하게 대처할 수 있는 구조를 만드는 것이 핵심입니다. 컴포넌트를 화면 단위로 설계하는 것과 의미 있는 단위로 분리하는 것 사이에는 큰 차이가 있습니다. 후자의 방식은 유지보수성과 코드의 가독성을 크게 향상시킵니다.Google의 Compose API 가이드라인에 따르면, 하나의 컴포넌트는 단일 문제를 해결하는 데 초점을 맞춰야 하며, 이를 통해 간결하고 사용하기 쉬운 API 설계를 지원할 수 있습니다. 또한, 선언형 UI의 대표 주자인 React의 설계 원칙과 선언적 컴포넌트 구조에서 배울 점이 많습니다. Compose는 React에 비.. 2024. 12. 13.
XML 에서 Copose로 전환하면 좋은 이유 1. UI와 로직의 통합기존의 XML 방식은 UI와 로직을 분리되었을때의 문제점은 다음과 같습니다.1-1. 데이터 바인딩의 실행 순서 문제XML에서 DataBinding을 활용하여 UI와 데이터를 연결하는 경우, XML이 먼저 실행되고 로직이 나중에 실행됩니다. 이로 인해 다음과 같은 문제가 발생합니다.초기 상태 비정상: UI 컴포넌트가 초기 상태를 올바르게 반영하지 못하거나, 데이터가 준비되기 전에 XML이 렌더링되어 잘못된 값을 표시할 수 있습니다.복잡한 상태 관리: XML의 뷰 상태와 로직의 데이터 상태를 일치시키기 위해 추가적인 코드가 필요합니다.1-2. 레이아웃 측정의 제약XML에서 커스텀 뷰를 만들거나 동적으로 뷰의 크기를 변경해야 하는 경우, onMeasure와 같은 메서드에서 크기를 측정하.. 2024. 12. 13.
Android Test - 3. Instrumented Test 안드로이드 프레임워크가 연관되는 모듈은 jvm 위에서는 실행할 수가 없다. 따라서 테스트를 에뮬레이터나 실기기 위에서 수행해야 되는데 이것을 인스트루먼티드 유닛 테스트라고 합니다 여기서는 예제로 메인 액티비티를 생성한 다음에 그 라이프사이클 상태를 확인하는 인스트루먼티드 테스트를 해보겠습니다 우선은 테스트에 필요한 Dependency를 추가해줍니다 androidTestImplementation "androidx.test:core:1.5.0" androidTestImplementation "androidx.test.ext:trush:1.5.0" androidTestImplementation "androidx.test:rules:1.5.0" 다음은 MainActivity 클래스로부터 테스트를 작성해 보겠습니.. 2024. 3. 31.
Android Test - 2. Local Test Build.Gradle 아래와 같이 기본으로 아래와 같이 Testing에 관련된 라이브러리가 추가 되어 있다. 이 testImplement와 androidTestImplementation 키워드를 볼 수 있다. jvm 만으로 수행할 수 있는 로컬 단위 테스트에 사용되는 Dependency 는 testImplement 이고 안드로이드 에뮬레이터나 실기기를 필요로 하는 계측 단위 테스트에 사용되는 Dependency는 androidTestImplementation 이다. 그러므로 이 기본 프로젝트에는 로컬 테스트용으로 JUnit4가 추가되고 계층 테스트용으로 JUnit Extension 그리고 Espresso가 추가되는 것 이다. 여기에 Assertion을 더 읽기 용이하게 해주는 Truth와 테스트를 크기.. 2024. 3. 31.
Android Test - 1. Basic 이번 글은 TestCode 도입에 앞서 지식에 대해서 정의하려고 한다. The History of Software Testing에서는 소프트웨어 테스팅이 1822년 찰스 베비지의 차분 엔진 제작과 함께 시작되었다고 하고 Bug라는 단어는 토마스 에디슨이 1878년 동료에게 보내는 편지에서 처음 사용된 것으로 알려져 있다. 따라서 테스트 방법론에서는 서로 대립하는 수많은 주장이 긴 시간동안 개발되어져 왔다. 테스트 유형이 피라미드 구조를 가져야 한다는 사람도 있고 아이스크림 콘 형태를 가져야한다는 사람도 있다. 또 테스트 커버리지는 100%를 목표해야 한다는 사람도 있고 혹은 100%를 목표하는 것은 무의미한 일이라고 말하기도 한다. 또 테스트를 먼저할지 구현이 먼저되어야 하는지에 대한 의견이 분분하다. .. 2024. 3. 31.
공유플로우와 상태플로우 플로우는 콜드 데이터 스트림이기 때문에 요청할 때마다 값이 계산이 된다. 이 때 여러개의 수신자 가 하나의 데이터가 변경되는지 감지하는 경우가 생길 수 있다. 이때 공유플로우를 사용할 수 있다. 공유플로우 공유플로우를 통해 메세지를 방출하면 대기하고 있던 모든 코루틴이 수신하게 된다. suspend fun main() { coroutineScope { val flow = MutableSharedFlow() launch { flow.collect(::println) } delay(1000) flow.emit("message 1") flow.emit("message 2") } } // 1초후 message 1 message 2 프로그램은 게속 실행됨 해당 flow 를 중지시킬려면 부모 코루틴인 corouti.. 2024. 3. 24.
플로우 생명주기 함수 플로우에는 여러가지 생명주기 함수가 있다. (중간 함수) 하나씩 알아보자. onEach onEach 함수는 값을 하나씩 받는다. 따라서 onEach 함수에 delay를 넣으면 값을 받아올 때마다 delay 함수가 실행이 된다. suspend fun main() { flowOf(1,2,3).onEach { delay(1000) }.collect(::println) } // 1초 후 1 1초 후 2 1초 후 3 onStart onStart 함수는 이름 그대로 collect 최종 연산이 실행될 때 호출되는 리스너 이다. 첫 번째 원소를 요청했을 때 호출되는 것을 확인할 수 있다. suspend fun main() { flowOf(1, 2, 3).onStart { println("start") }.collect.. 2024. 3. 24.
플로우 만들기 원시값을 가지는 플로우 플로우를 만드는 가장 간단한 방법은 플로우가 어떤 값을 가져야 하는지 정의하는 flowOf를 사용하는 것 이다. listOf 함수와 비슷하게 사용할 수 있다. suspend fun main() { flowOf(1, 2, 3).collect(::println) } // 1 2 3 만약 빈 flow 를 생성하고 싶다면 EmptyFlow 를 사용하면 된다. suspend fun main() { emptyFlow().collect(::println) } // 출력 x 컨버터 asFlow 함수를 사용해 컬렉션 을 flow 로 변환할 수 있다. asFlow 로 변환할 수 있는 컬렉션은 Iterable , Iterator , Sequence 이다. suspend fun main() { list.. 2024. 3. 24.
플로우의 실제 구현 플로우가 실제 어떻게 내부적으로 확인해 보자. 이를 하기위해 먼저 람다식을 만들어 실행하는 코드를 작성해 보자. fun main() { val f: () -> Unit = { print("1") print("2") print("3") println() } f() f() f() } // 123 123 123 결과는 누구나 예상할 수 있듯이 123 이 출력되고 있는 것을 확인할 수 있다. 흥미를 더 돋우기 위해 중단함수를 추가해 보자. suspend fun main() { val f: suspend () -> Unit = { print("1") delay(1000) print("2") delay(1000) print("3") println() } f() f() } // 123 // 2초 123 // 2초 람.. 2024. 3. 24.
핫 데이터 소스와 콜드 데이터 소스 Channel 코루틴의 통신방법중 Flow 개념 이전에 Channel 이 먼저 추가되었다. Channel 의 개념은 책에서는 이렇게 설명한다. Channel 은 도서관의 책들처럼 책을 빌리기위해 다른사람이 먼저 책을 반납해야한다. 즉 Channel 에는 송신자와 수신자의 제한이 없다. Channel 은 Hot Stream 으로 구독자의 여부에 관계없이 데이터가 생성이 된다. 하지만 구독을 할 때 데이터가 생성되는 것을 원할 때가 있다. [Cold Stream] 채널은 현재 개발하는 시점에는 자주 사용하지 않으므로, Hot - Cold stream 에 대해 더 자세히 알아보자. Hot vs Cold 위에서 말했다시피, Hot Stream 과 Cold Stream 은 다음과 같은 차이가 있다. Hot Str.. 2024. 3. 24.
플로우란 무엇인가? 플로우란 비동기적으로 처리해야할 값들의 데이터스트림이다. 즉 플로우 자체는 데이터를 모으고 그 끝에는 방출하는 역할을 한다. Flow 의 함수중 collect 가 그 역할을 한다. (== forEach ) 실제로 Flow 의 내부구현을 봐보면 유일한 멤버 함수인 collect 을 제외하고 다른 함수들은 확장 함수로 정의되어 있다. public interface Flow { public suspend fun collect(collector: FlowCollector) } 플로우와 값들을 나타내는 다른 방법들과 비교 여러개의 값들을 반환하고 싶을 때 List 같은 컬렉션을 사용할 수 있다. 아래 예제를 살펴보자. fun getList() = List(3){ Thread.sleep(1000) "list ite.. 2024. 3. 24.
Bitrise를 도입해 보자! 🛠 ■ Bitrise CI/CD 도입을 통한 배포 과정 자동화 및 효율성을 향상 시킨 경험 Background 리딩앤 서비스는 하나의 프로젝트와 사내 디자인 시스템, Utils, Viewer 등 5개 이상의 공통 모듈이 결합하여 형성되어 있음. 배포를 위해서는 프로젝트와 공통 모듈의 버전을 세팅한 후 APK를 추출하고, 이를 다운로드 할 수 있는 웹 페이지를 생성하여 QA 팀에 전달. Problem Situation 개발자의 실수로 잘못된 버전의 모듈로 APK 파일을 추출하여 전달해 잘못된 파일을 QA 검증하는 경우가 있었음. 웹페이지 생성 및 전달, Slack 알림 등 복잡하고 많은 리소스가 필요함. Troubleshooting & Result 사내 모바일 개발자들과 함께 Bitrise를 도입하여 CI.. 2024. 3. 23.
디자인 시스템의 텍스트 크기 문제 해결 경험 ■ 디자인 시스템의 텍스트 크기 문제 해결 경험 Background 사내 디자인 시스템에서 텍스트 크기는 DP(Density Pixel) 단위로 정의되어 있었으며, SP(Scale Pixel) 단위의 크기는 고려되지 않고 있었음. 사내 안드로이드 개발자들은 관습적으로 SP 단위로 개발해왔음. Problem Situation 리딩앤 서비스는 10세 미만의 어린이와 부모가 사용하기 때문에, 안드로이드 디바이스의 폰트 설정을 바꾸는 경우가 종종 있어 고려되지 않았던 SP 단위에 의해 예상하지 못한 UI가 나타나는 문제가 발생함 Troubleshooting 디자이너와 개발자들에게 이 문제를 주도적으로 알리고, 디자인 시스템의 Typography 크기에 대한 규칙을 재 정의하고 수정함 ■ 텍스트 컴포넌트 개선을 .. 2024. 3. 23.
MedioPlayer에서 ExoPlayer로 전환하는 이유(2) 이번에는 개인프로젝트에 Music 기능을 개발해보자. 해당 프로젝트는 GitHub 에서 Audio/video 칸의 Audio를 들어가면 코드를 확인해 볼 수 있다. 먼저 Exoplayer를 위한 개인 프로젝트 도식화가 되겠다. 2024. 3. 20.
3장 채널과 플로우 - 채널 코루틴끼리의 통신을 위한 기본적인 방법으로 채널 API가 추가되었다. 많은 사람들은 채널을 파이프로 떠올리지만 필자는 책을 교환하는데 사용되는 공공 책장과 비슷하다고 한다. 채널은 송신자와 수신자의 수의 제한이 없으며, 채널을 통해 전송된 모든 값은 단 한번만 받을 수 있다. Channel은 두개의 서로 다른 인터페이스를 구현한 하나의 인터페이스다. SendChennel은 원소를 보내거나(또는 더하거나) 채널을 닫는 용도로 사용된다. ReceiveChennel 은 원소를 받을 때(또는 꺼낼때) 사용된다. interface SendChannel { suspend fun send(element: E) fun close(): Boolean //.. } interface ReceiveChannel { suspe.. 2024. 3. 8.