Jetpack Compose 풍미의 클린 아키텍처

깨끗한 아키텍처를 사용하면 결합도가 매우 낮고 데이터베이스 및 프레임워크와 같은 기술 구현 세부 사항과 독립적인 애플리케이션을 설계할 수 있습니다. 이렇게 하면 응용 프로그램을 유지 관리하기 쉽고 유연하게 변경할 수 있습니다. 또한 본질적으로 테스트 가능해집니다. 여기에서는 클린 아키텍처 프로젝트를 구성하는 방법을 보여 드리겠습니다. 이번에는 Jetpack Compose를 사용하여 Android todo 애플리케이션을 빌드할 것입니다. API에서 검색된 todo를 나열하는 한 가지 사용 사례만 설명합니다. 시작하자.

프로젝트의 패키지 구조는 다음 형식을 취합니다.



바닥부터 시작하여

PRESENTATION 레이어는 모든 UI 관련 코드를 유지합니다. 이 경우 todos 목록의 보기 및 보기 모델이 됩니다.

DOMAIN 계층은 모든 비즈니스 로직을 유지하고 프로젝트 방문자에게 코드가 수행하는 방식이 아니라 수행하는 작업에 대한 좋은 아이디어를 제공합니다. 이 레이어에는 다음이 있습니다.

  • 사용 사례: 사용 사례당 하나의 파일,

  • 저장소: 저장소 인터페이스

  • 모델: 비즈니스 로직 사용 사례 및 UI에서 참조할 Todo와 같은 비즈니스 모델

  • 데이터 계층:

  • 저장소: 저장소 구현

  • DataSource: 모든 데이터 소스 인터페이스 및 데이터 소스 엔터티. 이러한 엔터티는 도메인 모델과 다르며 API의 요청 및 응답 개체에 직접 매핑됩니다.

  • 마지막으로 CORE 레이어는 상수, 구성 또는 의존성 주입(우리가 다루지 않을 것임)과 같은 모든 레이어에서 공통적인 모든 구성 요소를 유지합니다.



    우리의 첫 번째 작업은 항상 도메인 모델 및 데이터 엔터티로 시작하는 것입니다.

    data class Todo(
        val id: Int,
        val isCompleted: Boolean,
        val task: String
    
    )
    
    



    data class TodoAPIEntity(
        val id: Int,
        val completed: Boolean,
        val title: String
    )
    
    fun TodoAPIEntity.toTodo(): Todo {
        return Todo(
            id = id,
            isCompleted = completed,
            task = title
        )
    }
    


    이제 TodoDatasource에 대한 인터페이스를 작성해 보겠습니다. 데이터 소스(api, db 등)의 작동 방식을 적용하려면 하나가 필요합니다.

    import za.co.nanosoft.cleantodo.Domain.Model.Todo 
    interface TodoDataSource {    
        suspend fun getTodos(): List<Todo>
    }
    


    이 인터페이스의 구현을 작성하기에 충분하며 이를 TodoAPIImpl이라고 합니다.

    
    interface TodoApi {
    
        @GET("todos")
        suspend fun getTodos(): List<TodoAPIEntity>
    
        companion object {
            var todoApi: TodoApi? = null
            fun getInstance(): TodoApi {
                if (todoApi == null) {
                    todoApi = Retrofit.Builder()
                        .baseUrl(BASE_URL)
                        .addConverterFactory(GsonConverterFactory.create())
                        .build().create(TodoApi::class.java)
                }
                return todoApi!!
            }
        }
    }
    
    class TodoAPIImpl : TodoDataSource {
    
        override suspend fun getTodos(): List<Todo> {
            return TodoAPI.getInstance().getTodos().map { it.toTodo() }
        }
    }
    


    참고: 이 저장소의 getTodos 함수는 Todo 목록을 반환합니다. 따라서 TodoEntity -> Todo를 매핑해야 합니다.

    TodoRepositoryImpl을 작성하기 전에 도메인 계층에 대한 인터페이스를 작성하겠습니다.

    interface TodoRepository {
        suspend fun getTodos(): List<Todo>
    
    }
    



    class TodoRepositoryImpl(private val datasource: TodoDataSource) : TodoRepository {
    
        override suspend fun getTodos(): List<Todo> {
            return datasource.getTodos()
        }
    }
    
    


    이제 TodoRepositoryImpl이 데이터 소스를 교체하는 데 유용한 종속성으로 모든 데이터 소스를 사용할 수 있음을 알 수 있습니다.

    이제 todo 저장소가 있으므로 GetTodos 사용 사례를 코딩할 수 있습니다.

    class GetTodos(
        private val repository: TodoRepository
    ) {
        suspend operator fun invoke(): List<Todo> {
            return repository.getTodos()
        }
    }
    


    그런 다음 프레젠테이션의 뷰 모델과 뷰를 작성할 수 있습니다.

    class TodoViewModel constructor(
        private val getTodosUseCase: GetTodos
    ) : ViewModel() {
        private val _todos = mutableStateListOf<Todo>()
    
        val todos: List<Todo>
            get() = _todos
    
    
        suspend fun getTodos() {
            viewModelScope.launch {
                _todos.addAll(getTodosUseCase())
            }
        }
    }
    



    @Composable
    fun TodoListView(vm: TodoViewModel) {
    
        LaunchedEffect(Unit, block = {
            vm.getTodos()
        })
    
        Scaffold(
            topBar = {
                TopAppBar(
                    title = {
                        Text("Todos")
                    }
                )
            },
            content = {
                Column(modifier = Modifier.padding(16.dp)) {
                    LazyColumn(modifier = Modifier.fillMaxHeight()) {
                        items(vm.todos) { todo ->
                            Row(modifier = Modifier.padding(16.dp)) {
                                Checkbox(checked = todo.isCompleted, onCheckedChange = null)
                                Spacer(Modifier.width(5.dp))
                                Text(todo.task)
                            }
                            Divider()
                        }
                    }
                }
            }
        )
    }
    



    class MainActivity : ComponentActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            val vm = TodoViewModel(
                getTodosUseCase = GetTodos(
                    repository = TodoRepositoryImpl(
                        api = TodoAPIImpl()
                    )
                )
            )
            super.onCreate(savedInstanceState)
            setContent {
                CleantodoTheme {
                    TodoListView(vm)
                }
            }
        }
    }
    


    요약하자면:



    좋은 웹페이지 즐겨찾기