코틀린 안드로이드 앱개발 강좌, 뉴스 앱 만들기

안드로이드 앱 개발에 있어 Kotlin은 강력하고 현대적인 프로그래밍 언어로 자리 잡고 있습니다. 본 강좌에서는 Kotlin을 활용하여 실제 뉴스 앱을 만드는 방법을 단계별로 안내합니다. 앱은 외부 API로부터 뉴스를 가져와 사용자에게 보여주는 구조를 가지고 있습니다. 이 글을 통해 안드로이드 애플리케이션 아키텍처, 네트워킹, UI/UX 디자인 및 기타 필수 기능들을 익힐 수 있습니다.

1. 프로젝트 설정

이번 프로젝트의 첫 번째 단계는 Android Studio에서 새로운 프로젝트를 설정하는 것입니다. 먼저, Android Studio를 열고 새로운 프로젝트를 시작합니다.

  • 프로젝트 이름: NewsApp
  • 패키지 이름: com.example.newsapp
  • 언어: Kotlin
  • 기기: Phone and Tablet
  • API Level: API 21: Android 5.0 (Lollipop)

프로젝트가 생성되면 AndroidManifest.xml 파일을 열고 아래와 같이 인터넷 권한을 추가합니다.

<uses-permission android:name="android.permission.INTERNET" />

2. 뉴스 API 선택

뉴스 앱을 제작하기 위해 사용할 API를 선택합니다. 여기서는 NewsAPI를 사용합니다. 결과를 JSON 형식으로 반환하며 쉽게 사용할 수 있도록 구성되어 있습니다.

API 키를 발급받으려면 해당 사이트에 회원가입 후 API 키를 생성하세요.

3. 네트워킹 라이브러리 추가

Android Jetpack의 Retrofit을 사용하여 네트워크 요청을 관리합니다. Retrofit 라이브러리를 build.gradle 파일에 추가합니다.

dependencies {
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
    implementation 'com.squareup.okhttp3:logging-interceptor:4.9.0'
}

Gradle 파일을 sync합니다.

4. 데이터 모델 클래스 생성

뉴스 API로부터 받아올 데이터를 매핑하기 위해 데이터 클래스를 생성합니다. ArticleNewsResponse 클래스를 만들어 보세요.

data class Article(
    val source: Source,
    val author: String?,
    val title: String,
    val description: String?,
    val url: String,
    val urlToImage: String?,
    val publishedAt: String,
    val content: String?
)

data class Source(
    val id: String?,
    val name: String
)

data class NewsResponse(
    val status: String,
    val totalResults: Int,
    val articles: List<Article>
)

5. Retrofit 인터페이스 정의

NewsAPI와 통신하기 위한 Retrofit 인터페이스를 정의합니다. 이를 통해 HTTP 요청을 구성하게 됩니다.

interface NewsApiService {
    @GET("v2/top-headlines")
    suspend fun getTopHeadlines(
        @Query("country") country: String = "us",
        @Query("apiKey") apiKey: String
    ): NewsResponse
}

6. Retrofit 인스턴스 생성

Retrofit 인스턴스를 생성하여 API 호출을 준비합니다. Singleton 패턴을 사용해 인스턴스를 관리합니다.

object RetrofitInstance {
    private const val BASE_URL = "https://newsapi.org"

    private val retrofit by lazy {
        Retrofit.Builder()
            .addConverterFactory(GsonConverterFactory.create())
            .baseUrl(BASE_URL)
            .build()
    }

    val api: NewsApiService by lazy {
        retrofit.create(NewsApiService::class.java)
    }
}

7. ViewModel 및 LiveData 활용

MVVM 패턴을 적용하기 위해 ViewModel과 LiveData를 사용하여 UI와 데이터를 분리합니다. ViewModel을 생성하여 뉴스 데이터를 관리합니다.

class NewsViewModel(private val apiKey: String) : ViewModel() {
    private val _news = MutableLiveData<List<Article>>()
    val news: LiveData<List<Article>> get() = _news

    fun fetchTopHeadlines() {
        viewModelScope.launch {
            val response = RetrofitInstance.api.getTopHeadlines(apiKey = apiKey)
            _news.postValue(response.articles)
        }
    }
}

8. UI 설계

MainActivity의 레이아웃 파일을 수정하여 RecyclerView를 추가합니다. 다음은 activity_main.xml의 예입니다.

<androidx.recyclerview.widget.RecyclerView
    android:id="@+id/recyclerView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:listitem="@layout/item_article"/>

또한 각 뉴스 아이템을 표시하기 위한 레이아웃 item_article.xml을 생성합니다.

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:scaleType="centerCrop"/>

    <TextView
        android:id="@+id/titleTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="18sp"
        android:padding="8dp"/>

    <TextView
        android:id="@+id/descriptionTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="14sp"
        android:padding="8dp"/>

</LinearLayout>

9. RecyclerView 어댑터 구성

뉴스 데이터를 화면에 표시하기 위해 RecyclerView.Adapter를 구현합니다.

class NewsAdapter(private val articles: List<Article>) : RecyclerView.Adapter<NewsAdapter.NewsViewHolder>() {

    inner class NewsViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val titleTextView: TextView = itemView.findViewById(R.id.titleTextView)
        val descriptionTextView: TextView = itemView.findViewById(R.id.descriptionTextView)
        val imageView: ImageView = itemView.findViewById(R.id.imageView)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NewsViewHolder {
        val itemView = LayoutInflater.from(parent.context).inflate(R.layout.item_article, parent, false)
        return NewsViewHolder(itemView)
    }

    override fun onBindViewHolder(holder: NewsViewHolder, position: Int) {
        val currentArticle = articles[position]
        holder.titleTextView.text = currentArticle.title
        holder.descriptionTextView.text = currentArticle.description
        // 이미지 로딩은 Glide 또는 Picasso 같은 라이브러리로 처리
    }

    override fun getItemCount() = articles.size
}

10. MainActivity에 데이터 바인딩

MainActivity에서 RecyclerView를 설정하고 ViewModel을 통해 데이터를 가져옵니다.

class MainActivity : AppCompatActivity() {
    private lateinit var newsViewModel: NewsViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val recyclerView: RecyclerView = findViewById(R.id.recyclerView)
        recyclerView.layoutManager = LinearLayoutManager(this)

        val apiKey = "YOUR_API_KEY" // API 키 입력
        newsViewModel = ViewModelProvider(this, ViewModelFactory(apiKey)).get(NewsViewModel::class.java)

        newsViewModel.fetchTopHeadlines()
        newsViewModel.news.observe(this, { articles ->
            val adapter = NewsAdapter(articles)
            recyclerView.adapter = adapter
        })
    }
}

11. 사용자 인터페이스 개선

각 뉴스 아이템에 클릭 이벤트를 추가하여 사용자가 기사를 클릭하면 해당 URL로 이동할 수 있도록 합니다.

class NewsAdapter(private val articles: List<Article>, private val context: Context) : RecyclerView.Adapter<NewsAdapter.NewsViewHolder>() {
    inner class NewsViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val titleTextView: TextView = itemView.findViewById(R.id.titleTextView)
        val descriptionTextView: TextView = itemView.findViewById(R.id.descriptionTextView)
        val imageView: ImageView = itemView.findViewById(R.id.imageView)

        init {
            itemView.setOnClickListener {
                val position: Int = adapterPosition
                val article = articles[position]
                val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(article.url))
                context.startActivity(browserIntent)
            }
        }
    }
}

12. 달라지는 사용자 경험

앱의 사용자 경험을 개선하기 위해 ProgressBar를 추가하여 데이터를 로드할 때 사용자에게 진행 상황을 알립니다.

<ProgressBar
    android:id="@+id/progressBar"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center"/>

데이터 로드 중 ProgressBar를 보이게 하고 로딩이 끝나면 숨기도록 MainActivity 코드를 수정합니다.

newsViewModel.news.observe(this, { articles ->
    progressBar.visibility = View.GONE
    recyclerView.adapter = NewsAdapter(articles, this)
})

// 데이터 로딩 전 ProgressBar 보여주기
progressBar.visibility = View.VISIBLE

종합 및 결론

이번 강좌에서는 Kotlin을 사용하여 뉴스 앱을 만드는 전 과정을 살펴보았습니다. 네트워크 통신을 위한 Retrofit 설정, MVVM 아키텍처를 적용한 데이터 관리, RecyclerView 사용 등 여러분이 실제 앱 개발에 필요한 기초부터 고급 기술까지 익힐 수 있는 기회였습니다. 추가적으로, 디자인을 개선하거나 기능을 확장하여 더 나은 사용자 경험을 제공할 수 있는 방향으로 나아가면 됩니다.

이제 여러분만의 뉴스 앱을 만들 준비가 되셨습니다! 직접 실습하고 다양한 기능을 추가해 보세요.