서버 개발자가 서비스를 처음부터 끝까지 만들어보기 위한 프로젝트 The Peak입니다. The Peak는 Vertial social network service 의 컨셉입니다. 간단한 UI를 통해서 서비스 방향과 컨셉을 명확히 하기 위해서 안드로이드 앱을 먼저 개발할 예정입니다. 그 첫 단계로 IDE(Android Studio) 설치과 전체적으로 가져갈 아키텍처를 조사하였습니다.

Java 혹은 Kotlin을 사용할 줄 알지만 처음으로 안드로이드 프로젝트 혹은 공부하는 분들에게 도움이 되길 바랍니다.

Android 개발 환경 구축

Android 개발은 구글에서 공식적으로 제공하고 있는 Android Studio를 사용합니다.

$ brew cask install android-studio

구글에서 검색한 다른 설치 가이드를 따르면 android-sdk 설치 이후 android-studio를 설치하라고 가이드 하고 있습니다. 하지만 구글의 Android Studio 설치 가이드를 살펴보니 android-sdk를 먼저 설치할 필요는 없어서 android-studio를 설치하고 android-studio에서 필요한 sdk 및 avd를 구성하였습니다.

설치 시에는 항상 기본 설정을 최대한 유지하고 테마만 dracula로 변경하였습다. IntelliJ IDEA 사용할 때도 유일하게 변경한 설정이 Theme 입니다. 꽤 오랜 시간 다운로드 및 설치를 수행하고 android-studio를 설치 완료하였습니다.

Project 생성 및 실행

Kotlin을 좋아하고 서버도 Kotlin으로 구축 예정이기 때문에 Kotlin 기반의 프로젝트를 구성하였습니다. 실제 안드로이드 폰이 없기 때문에 AVD를 통해 실행한 화면은 다음과 같습니다.

thepeak-project-with-android-studio

처음으로 안드로이드 개발을 하기 때문에 각 요소의 선택 기준은 다음과 같습니다.

Kotlin

Kotlin 기반의 프로젝트임에도 불구하고 src/java 밑에 kotlin 코드가 생성되었습니다. Kotlin 코드 추가 | Android Developers를 확인하니 Kotlin 파일과 Java 파일을 모두 한 곳에서 보는 것이 더 쉽게 느껴질 수도 있다 라는 이유로 src/java 아래 kotlin 코드가 생성되었습니다. 개인적인 의견으로도 Java와 Kotlin을 혼용한다면 현재 구성이 좋다고 판단되어 유지하였습니다.

Activity

Android에서 Activity는 앱에서 보이는 화면입니다. 개발할 앱도 Vertical SNS이기 때문에 SNS에서 가장 많이 활용되는 Scroll 기반의 Activity를 생성하였습니다.

Activity는 일종의 애플리케이션 구성 요소로서, 사용자가 전화 걸기, 사진 찍기, 이메일 보내기 또는 지도 보기 등의 일을 하기 위해 상호작용할 수 있는 화면을 제공합 니다. 액티비티마다 창이 하나씩 주어져 이곳에 사용자 인터페이스를 끌어올 수 있습니다. 이 창은 일반적으로 화면을 가득 채우지만, 작은 창으로 만들어 다른 창 위에 띄울 수도 있습니다.
출처: https://developer.android.com/guide/components/activities?hl=ko

실제로 구현 시에는 View와 실제 Data 간의 연동이 필요한데 이를 위해서 MVVM 패턴을 활용할 예정입니다.

다음 코드는 자동으로 생성된 코드로 layout xml 파일과 코드가 연결되는 형식은 ID 값(예. fab)이 변수명으로 활용됩니다. 그리고 해당 UI Component에 setOnClickListener 함수를 통해 Click 이벤트에 대한 Listener를 통해 원하는 Action을 등록할 수 있습니다.

class DashboardActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_dashboard)
        setSupportActionBar(toolbar)
        fab.setOnClickListener { view ->
            Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
                .setAction("Action", null).show()
        }
    }

    override fun onCreateOptionsMenu(menu: Menu): Boolean {
        // Inflate the menu; this adds items to the action bar if it is present.
        menuInflater.inflate(R.menu.menu_dashboard, menu)
        return true
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.

        return when (item.itemId) {
            R.id.action_settings -> true
            else -> super.onOptionsItemSelected(item)
        }
    }
}

AVD (Android Virtual Device)

AVD는 실제 안드로이드 기기 없이 시뮬레이터로 테스트를 제공하는 기능이다.

다양한 안드로이드 기기를 지원하기 위해서 AVD에서 추천하는 Nexus 5X API 28로 일단 API를 설정하였다. 처음에는 100% 호환을 목표로 개발을 진행하며 최신 API가 절대적으로 필요한 경우 버전을 올릴 예정입니다.

Android 구조

IDE 구성 후 간단하게 안드로이드 프로젝트 코드를 확인하였다. 샘플로 생성된 코드는 MVC 개념으로 구성되어 있어서 바로 부딪히며 개발을 해도 되지만 좀 더 나은 구조를 위해 찾아본 구조는 다음과 같습니다.

안드로이드 구조

  1. MVC: View(layout xml) - Controller(Activity) - Model
  2. MVP: View(layout xml, Activity) - Presenter(Presenter, Impl) - Model
  3. MVVM: View(layout xml, Activity) - ViewModel(ViewModel, Binding) - Model

3가지 구조의 차이는 안드로이드의 안드로이드의 MVC, MVP, MVVM 종합 안내서에서 잘 설명하고 있습니다. 그 중에서도 이 프로젝트에서는 MVVM으로 개발을 하기로 선택하였습니다.

MVVM

MSDN The Model-View-ViewModel Pattern에서 제시된 개념이 안드로이드를 위해 Java/Kotlin으로 변화가 필요합니다. MVVM에서 가장 중요한 개념은 DataBinding을 통한 View -> ViewModel -> Model 의존성 관계입니다.

DataBinding (LiveData & ViewModel)

DataBinding은 구글에서 제공하는 Android Architecture Component(AAC)에서 제공하는 라이브러리를 이용하면 쉽게 구현할 수 있습니다. 특히 안드로이드 앱의 Lifecylce을 고려한다면 좋은 선택이 될 것이라고 생각됩니다.

DataBinding은 Observable을 표현해야하는데 이를 위해서 AAC는 LiveData를 제공(참고)하고 Activity의 Lifecylce에 따라 처리하기 위해서 ViewModel을 제공합니다.

class NameViewModel : ViewModel() {

    // Create a LiveData with a String
    val currentName: MutableLiveData<String> by lazy {
        MutableLiveData<String>()
    }

    // Rest of the ViewModel...
}

class NameActivity : AppCompatActivity() {

    private lateinit var mModel: NameViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Other code to setup the activity...

        // Get the ViewModel.
        mModel = ViewModelProviders.of(this).get(NameViewModel::class.java)


        // Create the observer which updates the UI.
        val nameObserver = Observer<String> { newName ->
            // Update the UI, in this case, a TextView.
            mNameTextView.text = newName
        }

        // Observe the LiveData, passing in this activity as the LifecycleOwner and the observer.
        mModel.currentName.observe(this, nameObserver)
    }
}

위 샘플 코드는 NameViewModel를 통해서 currentName가 변할 때 mNameTextView.text 값이 변화하는 Binding을 구현한 예제입니다.

Persistence

앱, 서버 등과 무관하게 Data에서 중요한 것은 생명주기라고 생각합니다. 안드로이드 앱에서 영구성은 Room persistence library을 이용하면 쉽게 구현 가능합니다.

@Entity
data class User(
    @PrimaryKey var uid: Int,
    @ColumnInfo(name = "first_name") var firstName: String?,
    @ColumnInfo(name = "last_name") var lastName: String?
)

@Dao
interface UserDao {
    @Query("SELECT * FROM user")
    fun getAll(): List<User>

    @Query("SELECT * FROM user WHERE uid IN (:userIds)")
    fun loadAllByIds(userIds: IntArray): List<User>

    @Query("SELECT * FROM user WHERE first_name LIKE :first AND " +
           "last_name LIKE :last LIMIT 1")
    fun findByName(first: String, last: String): User

    @Insert
    fun insertAll(vararg users: User)

    @Delete
    fun delete(user: User)
}

@Database(entities = arrayOf(User::class), version = 1)
abstract class AppDatabase : RoomDatabase() {
    abstract fun userDao(): UserDao
}

val db = Room.databaseBuilder(
            applicationContext,
            AppDatabase::class.java, "database-name"
        ).build()

위 예제처럼 Dao 정의를 통해서 쉽게 SQLlite를 위한 인터페이스 구현이 제공됩니다. MyBatis, Hibernate를 사용해보았다면 쉽게 적응이 가능한 일반적인 형태로 생각됩니다.


오늘의 개발은 여기까지

안드로이드 개발이 처음이지만 기본으로 생성되는 프로젝트의 코드들을 훑어 보았을 때 .NET의 Winform, WPF, Java의 JavaFX에서 사용했던 MVC, MVVM 패턴들과 유사한 UI 코드들이 인상적이었습니다. 서버 개발자 이전에 Desktop 개발자이기도 하였던^^; 경험이 계속 활용됨이 그래도 예전에 열심히 Desktop 개발을 했던게 좋았구나 라는 생각이 들었습니다.

프로그래밍에서 기본 개념은 역시 가장 중요한 것 같습니다.

그래서 추억을 다시 바라보며 안드로이드 개발에서도 MVVM(.NET의 추억이여~)을 통해서 구현할 예정입니다. 반대로 이 경험이 서버 개발에도 많은 부분 활용될 수 있을 것이라고 믿습니다. 서버 개발을 하듯이 안드로이드 앱 개발 과정을 계속 진행하고 글을 올리도록 하겠습니다.