2026/04/18

오늘의 이야기

Android Studio를 사용하여 Google Maps 연동 앱 만들기


구글맵



 


소개


이번 포스트에서는 Android Studio와 Jetpack Compose를 사용하여 Google Maps를 연동하는 방법을 알아보겠습니다. 이를 통해 앱에서 지도를 표시하고, 사용자가 지도를 길게 클릭했을 때 해당 위치의 좌표를 얻는 기능을 구현할 수 있습니다.


준비물



  • Android Studio 설치

  • Google Cloud Platform 계정 및 프로젝트 생성

  • Google Maps API Key 발급


단계별 구현


1. 프로젝트 생성 및 설정



  1. 새 프로젝트 생성:

    • Android Studio에서 새 프로젝트를 생성하고 "Empty Compose Activity" 템플릿을 선택합니다.



  2. Google Maps API Key 발급:

    • Google Cloud Platform에 로그인하고 프로젝트를 생성합니다.

    • "API 및 서비스" > "라이브러리"로 이동하여 "Maps SDK for Android"를 활성화합니다.

    • "API 및 서비스" > "인증 정보"로 이동하여 "API 키 만들기"를 클릭합니다.

    • 생성된 API 키를 복사해둡니다.



  3. 프로젝트 설정:

    • build.gradle (Project: <your_project_name>) 파일에 Google Maven 저장소를 추가합니다:

      allprojects {
      repositories {
      google()
      mavenCentral()
      }
      }



    • build.gradle (Module: app) 파일에 필요한 의존성을 추가합니다:


       



      dependencies {
      implementation 'com.google.accompanist:accompanist-permissions:0.30.0'
      implementation 'com.google.maps.android:maps-compose:2.1.0'
      implementation 'com.google.android.gms:play-services-maps:18.0.2'
      }




  4. AndroidManifest.xml 설정:


<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.yourapp">

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

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MyApplication">

<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="YOUR_API_KEY_HERE" />

<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

 


2. MainActivity 설정



  1. MainActivity.kt 파일을 열어 Jetpack Compose와 Google Maps를 사용하여 지도를 표시하고, 지도에서 길게 클릭한 위치의 좌표를 얻습니다:

    package com.example.yourapp

    import android.os.Bundle
    import android.widget.Toast
    import androidx.activity.ComponentActivity
    import androidx.activity.compose.setContent
    import androidx.compose.foundation.layout.fillMaxSize
    import androidx.compose.material3.MaterialTheme
    import androidx.compose.material3.Surface
    import androidx.compose.runtime.Composable
    import androidx.compose.runtime.mutableStateOf
    import androidx.compose.runtime.remember
    import androidx.compose.ui.Modifier
    import androidx.compose.ui.platform.LocalContext
    import com.google.android.gms.maps.model.LatLng
    import com.google.maps.android.compose.*

    class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent {
    MyApplicationTheme {
    Surface(
    modifier = Modifier.fillMaxSize(),
    color = MaterialTheme.colorScheme.background
    ) {
    MapScreen()
    }
    }
    }
    }
    }

    @Composable
    fun MapScreen() {
    val context = LocalContext.current
    val cameraPositionState = rememberCameraPositionState {
    position = com.google.android.gms.maps.CameraPosition.fromLatLngZoom(LatLng(0.0, 0.0), 10f)
    }

    val selectedPosition = remember { mutableStateOf<LatLng?>(null) }

    GoogleMap(
    modifier = Modifier.fillMaxSize(),
    cameraPositionState = cameraPositionState,
    onMapLongClick = { latLng ->
    selectedPosition.value = latLng
    Toast.makeText(context, "Long clicked at: ${latLng.latitude}, ${latLng.longitude}", Toast.LENGTH_SHORT).show()
    }
    )

    selectedPosition.value?.let { position ->
    Marker(
    state = MarkerState(position = position),
    title = "Selected Position"
    )
    }
    }




결과


이제 앱을 실행하면 지도가 표시되고, 사용자가 지도를 길게 클릭할 때마다 해당 위치의 좌표가 토스트 메시지로 표시되며, 선택된 위치에 마커가 추가됩니다.





오늘의 이야기

android studio 의 AVD 의 화면은 왜 블랙 스크린이 되는 걸까 ?

 


풍경이 있는 그림



 


Android Studio의 AVD(안드로이드 가상 디바이스)가 블랙 스크린이 되는 일반적인 이유는 다음과 같습니다:





  1. 그래픽 가속 문제:

    • AVD는 가상화된 환경에서 실행되므로 그래픽 가속이 제대로 설정되지 않으면 블랙 스크린 문제가 발생할 수 있습니다. AVD 설정에서 그래픽 옵션을 Automatic 또는 Software로 변경해보세요.



  2. 불충분한 시스템 리소스:

    • 가상 디바이스를 실행하는 데 필요한 시스템 리소스(CPU, 메모리 등)가 충분하지 않으면 블랙 스크린 문제가 발생할 수 있습니다. 다른 불필요한 프로그램을 종료하여 시스템 리소스를 확보하세요.



  3. HAXM 설치 문제:

    • Intel의 HAXM(Hardware Accelerated Execution Manager)이 제대로 설치되지 않았거나 활성화되지 않은 경우 문제가 발생할 수 있습니다. HAXM이 제대로 설치되어 있는지 확인하고, 설치되어 있지 않다면 설치해보세요.



  4. AVD 구성 문제:

    • AVD 설정이 올바르지 않을 경우 문제가 발생할 수 있습니다. AVD Manager에서 가상 장치를 삭제하고 새로 만들거나, 다른 장치 설정으로 테스트해보세요.



  5. 그래픽 드라이버 문제:

    • 그래픽 드라이버가 최신 상태가 아닐 경우 문제가 발생할 수 있습니다. 그래픽 드라이버를 최신 버전으로 업데이트해보세요.



  6. Android Studio 버그:

    • 간혹 Android Studio 자체의 버그로 인해 문제가 발생할 수 있습니다. 최신 버전의 Android Studio로 업데이트하거나, 문제가 지속될 경우 이전 버전으로 다운그레이드해보세요.




해결 방법



  1. 그래픽 가속 설정 변경:

    • AVD Manager에서 문제의 가상 장치를 선택하고, Edit 버튼을 클릭합니다.

    • Graphics 옵션을 Automatic에서 Software로 변경한 후 다시 시도해보세요.



  2. HAXM 설치 확인:

    • SDK Manager에서 SDK Tools 탭을 선택하고, Intel x86 Emulator Accelerator (HAXM installer)가 설치되어 있는지 확인하세요.

    • 설치되어 있지 않다면, 체크하고 Apply 버튼을 눌러 설치하세요.



  3. 시스템 리소스 확인:

    • 작업 관리자를 열어 시스템 리소스 사용량을 확인하고, 불필요한 프로그램을 종료하여 리소스를 확보하세요.



  4. 그래픽 드라이버 업데이트:

    • 그래픽 드라이버를 최신 버전으로 업데이트하세요. 제조사 웹사이트에서 최신 드라이버를 다운로드하고 설치할 수 있습니다.




위의 방법들을 시도해보시면 AVD 블랙 스크린 문제를 해결하는 데 도움이 될 것입니다.








 

avd manager 에서 graphics 을 수정할 수 없다면 ?

 







 


AVD Manager에서 그래픽 설정을 수정할 수 없는 경우, 다음과 같은 방법을 시도해볼 수 있습니다:


1. config.ini 파일 직접 수정


AVD 설정 파일을 직접 수정하여 그래픽 설정을 변경할 수 있습니다.



  1. AVD 폴더 찾기:

    • AVD 설정 파일(config.ini)은 사용자 홈 디렉토리 내 .android/avd/ 폴더에 저장됩니다.

    • 예를 들어, Windows에서는 다음 경로에 있습니다: C:\Users\<YourUsername>\.android\avd\

    • macOS와 Linux에서는 다음 경로에 있습니다: /Users/<YourUsername>/.android/avd/



  2. config.ini 파일 열기:

    • 원하는 AVD 폴더로 이동한 후, 해당 AVD의 .avd 폴더 내부에 있는 config.ini 파일을 텍스트 편집기로 엽니다.



  3. 그래픽 설정 수정:

    • config.ini 파일 내에서 hw.gpu.mode와 관련된 항목을 찾아 수정합니다.

    • 예를 들어, 그래픽 모드를 software로 변경하려면 다음과 같이 설정합니다:

      hw.gpu.mode=software





  4. 변경 사항 저장:

    • 파일을 저장하고 AVD를 다시 실행해보세요.




 


2. 새로운 AVD 생성


기존 AVD의 설정을 변경할 수 없는 경우, 새로운 AVD를 생성하면서 그래픽 설정을 올바르게 설정할 수 있습니다.



  1. AVD Manager 열기:

    • Android Studio 상단 메뉴에서 Tools -> AVD Manager를 선택합니다.



  2. 새 AVD 생성:

    • Create Virtual Device... 버튼을 클릭합니다.



  3. 디바이스 선택:

    • 사용할 가상 디바이스를 선택하고 Next 버튼을 클릭합니다.



  4. 시스템 이미지 선택:

    • 사용할 시스템 이미지를 선택하고 Next 버튼을 클릭합니다.



  5. AVD 설정:

    • AVD 설정 화면에서 Graphics 옵션을 Automatic, Hardware, 또는 Software로 설정할 수 있습니다.

    • 설정을 완료한 후 Finish 버튼을 클릭하여 AVD를 생성합니다.




3. 그래픽 드라이버 업데이트


그래픽 드라이버가 최신 상태가 아닐 경우, AVD가 제대로 실행되지 않을 수 있습니다. 그래픽 드라이버를 최신 버전으로 업데이트해보세요.



  1. 그래픽 드라이버 확인:

    • 사용 중인 그래픽 카드의 드라이버를 제조사 웹사이트에서 최신 버전으로 다운로드합니다.

    • 예: NVIDIA, AMD, Intel



  2. 드라이버 설치:

    • 다운로드한 드라이버를 설치하고 시스템을 재부팅합니다.




위의 방법들을 통해 AVD의 블랙 스크린 문제를 해결할 수 있습니다. 만약 문제가 지속된다면, Android Studio를 최신 버전으로 업데이트하거나, 문제를 재현할 수 있는 환경을 자세히 설명하여 추가적인 도움을 요청하는 것도 좋은 방법입니다.









오늘의 이야기

Jetpack Compose를 사용하여 AdMob 배너 광고 추가하기


안녕하세요! 이번 블로그 포스트에서는 Jetpack Compose를 사용하여 안드로이드 앱에 AdMob 배너 광고를 추가하는 방법을 단계별로 설명드리겠습니다. Jetpack Compose는 기존의 XML 레이아웃 파일 대신 Kotlin 코드로 UI를 구성하는 방식으로, 더욱 간결하고 효율적으로 UI를 만들 수 있습니다. 그럼 시작해 보겠습니다!


1. AdMob 계정 생성 및 광고 단위 ID 얻기


먼저, AdMob 계정을 생성하고 로그인합니다. 그런 다음 새 앱을 등록하고 광고 단위 ID를 생성합니다. 이 ID는 나중에 필요합니다.


2. Gradle 설정


AdMob SDK를 추가하기 위해 build.gradle 파일을 수정합니다.


프로젝트 수준의 build.gradle 파일:


 

allprojects {
repositories {
google()
mavenCentral()
}
}

 


앱 수준의 build.gradle 파일:


plugins {
id 'com.android.application'
id 'kotlin-android'
}

android {
compileSdk 35

defaultConfig {
applicationId "com.example.mycomposeapp"
minSdk 21
targetSdk 35
versionCode 1
versionName "1.0"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary = true
}
}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
buildFeatures {
compose true
}
composeOptions {
kotlinCompilerExtensionVersion '1.4.0'
}
packagingOptions {
resources {
excludes += '/META-INF/{AL2.0,LGPL2.1}'
}
}
}

dependencies {
implementation 'androidx.core:core-ktx:1.10.0'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.0'
implementation 'androidx.activity:activity-compose:1.7.0-beta01'
implementation 'androidx.compose.ui:ui:1.4.0-beta01'
implementation 'androidx.compose.ui:ui-tooling-preview:1.4.0-beta01'
implementation 'androidx.compose.material3:material3:1.1.0-alpha06'
implementation 'com.google.android.gms:play-services-ads:22.1.0'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
androidTestImplementation 'androidx.compose.ui:ui-test-junit4:1.4.0-beta01'
debugImplementation 'androidx.compose.ui:ui-tooling:1.4.0-beta01'
debugImplementation 'androidx.compose.ui:ui-test-manifest:1.4.0-beta01'
}

3. AndroidManifest.xml 수정


AndroidManifest.xml 파일에 AdMob 앱 ID를 추가합니다.


 

<manifest>
<application>
<!-- Replace with your actual AdMob app ID -->
<meta-data
android:name="com.google.android.gms.ads.APPLICATION_ID"
android:value="ca-app-pub-xxxxxxxxxxxxxxxx~yyyyyyyyyy"/>
</application>
</manifest>

4. AdMob 초기화 및 배너 광고 추가


MainActivity.kt 파일을 수정하여 Jetpack Compose 내에서 AdMob 배너 광고를 로드합니다.


import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import com.google.accompanist.admob.AdMobBanner
import com.google.accompanist.admob.AdMobBannerSize
import com.google.android.gms.ads.MobileAds
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
MobileAds.initialize(this) {}

setContent {
MyApp {
AdMobBannerAd()
}
}
}
}

@Composable
fun MyApp(content: @Composable () -> Unit) {
Box(modifier = Modifier.fillMaxSize()) {
content()
}
}

@Composable
fun AdMobBannerAd() {
val context = LocalContext.current

AndroidView(
modifier = Modifier
.fillMaxWidth()
.padding(10.dp),
factory = { context ->
AdView(context).apply {
this.setAdSize(AdSize.BANNER)
adUnitId = context.getString(R.string.admob_banner_id)
loadAd(AdRequest.Builder().build())
}
}
)
}

 


이 예제에서는 Jetpack Compose를 사용하여 AdMob 배너 광고를 앱의 하단에 추가했습니다. 광고 단위 ID를 실제로 생성한 ID로 교체하는 것을 잊지 마세요. 이를 통해 Jetpack Compose 환경에서도 AdMob 광고를 성공적으로 통합할 수 있습니다.


 


배너광고 예시



 


추가적으로, 다른 유형의 광고(예: 전면 광고, 보상형 광고)를 추가하려면 AdMob 공식 문서를 참고하시기 바랍니다.


이상으로 Jetpack Compose를 사용하여 AdMob 배너 광고를 추가하는 방법에 대해 알아보았습니다. 도움이 되셨길 바랍니다! 질문이나 의견이 있으시면 댓글로 남겨주세요. 감사합니다.


 


 





오늘의 이야기


#스하리1000명프로젝트,
แพ้เกาหลีเหรอ? แม้ว่าคุณจะพูดภาษาเกาหลีไม่ได้ แต่แอปนี้จะช่วยให้คุณเดินทางได้อย่างง่ายดาย
เพียงพูดภาษาของคุณ ระบบจะแปล ค้นหา และแสดงผลลัพธ์เป็นภาษาของคุณ
เหมาะสำหรับนักเดินทาง! รองรับมากกว่า 10 ภาษา รวมถึงภาษาอังกฤษ ญี่ปุ่น จีน เวียดนาม และอื่นๆ
ลองตอนนี้!
https://play.google.com/store/apps/details?id=com.billcoreatech.opdgang1127




2026/04/17

오늘의 이야기

Jetpack Compose에서 알림 권한 요청과 알림 표시하기


Android 13(Tiramisu, API 33) 이상에서는 사용자가 알림을 수신하기 전에 POST_NOTIFICATIONS 권한을 요청해야 합니다. Jetpack Compose를 사용하여 권한을 확인하고 요청하는 방법을 알아보겠습니다.


1. 프로젝트 설정


먼저, build.gradle 파일에 필요한 의존성을 추가합니다:



dependencies {
implementation "androidx.core:core-ktx:1.9.0"
implementation "androidx.compose.ui:ui:1.3.3"
implementation "androidx.compose.material:material:1.3.1"
implementation "androidx.compose.ui:ui-tooling-preview:1.3.3"
implementation "androidx.activity:activity-compose:1.6.1"
}


2. 권한 확인 및 요청


다음은 Jetpack Compose를 사용하여 POST_NOTIFICATIONS 권한을 확인하고 요청하는 예제입니다:


 

import android.Manifest
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.*
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat
import androidx.core.content.PermissionChecker

class MainActivity : ComponentActivity() {

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

setContent {
RequestNotificationPermission()
}
}

@Composable
fun RequestNotificationPermission() {
val permissionState = remember { mutableStateOf(checkNotificationPermission()) }

val requestPermissionLauncher =
rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
permissionState.value = isGranted
}

if (permissionState.value) {
Text("Notification permission granted")
} else {
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text("Notification permission is required")
Spacer(modifier = Modifier.height(16.dp))
Button(onClick = {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
}
}) {
Text("Request Permission")
}
}
}
}

private fun checkNotificationPermission(): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED
} else {
true
}
}
}

3. 알림 생성 및 표시


권한을 얻은 후, 알림을 생성하고 표시하는 방법을 살펴보겠습니다:



import android.app.NotificationChannel
import android.app.NotificationManager
import android.os.Build
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat

fun showNotification(context: Context) {
// 알림 채널 설정 (Android 8.0 이상)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channelId = "your_channel_id"
val channelName = "Your Channel Name"
val importance = NotificationManager.IMPORTANCE_DEFAULT
val channel = NotificationChannel(channelId, channelName, importance).apply {
description = "Your Channel Description"
}
val notificationManager: NotificationManager = context.getSystemService(NotificationManager::class.java)
notificationManager.createNotificationChannel(channel)
}

// 알림 생성
val builder = NotificationCompat.Builder(context, "your_channel_id")
.setSmallIcon(R.drawable.notification_icon)
.setContentTitle("Your Notification Title")
.setContentText("Your Notification Content")
.setPriority(NotificationCompat.PRIORITY_HIGH)

// 알림 표시
with(NotificationManagerCompat.from(context)) {
notify(1, builder.build())
}
}


결론


이 글에서는 Jetpack Compose를 사용하여 Android 13 이상에서 POST_NOTIFICATIONS 권한을 요청하고 알림을 표시하는 방법을 알아보았습니다. 권한 관리와 알림 생성은 사용자 경험을 향상시키는 중요한 요소입니다. 이를 통해 더 나은 사용자 경험을 제공할 수 있습니다.


 


알림 도착 예시



 





오늘의 이야기

MutableLiveData에서 MutableStateFlow로 전환하기


앱 만들기 이미지



 


안녕하세요, 개발자 여러분! 오늘은 Android 개발에서 MutableLiveData를 MutableStateFlow로 전환하는 방법에 대해 알아보겠습니다. StateFlow는 Kotlin의 코루틴을 활용한 상태 관리 도구로, LiveData보다 더 많은 장점을 제공합니다. 그럼 시작해볼까요?


왜 MutableStateFlow로 전환해야 할까요?



  1. Null 안전성: LiveData는 null 값을 허용하지만, StateFlow는 초기 값을 필요로 하므로 null 안전성을 보장합니다.

  2. 수명 주기 독립성: LiveData는 UI 컴포넌트의 수명 주기에 의존하지만, StateFlow는 코루틴 스코프 내에서 동작하므로 더 유연합니다.

  3. 스레드 안전성: StateFlow는 스레드 안전성을 제공하여 동시성 문제를 줄여줍니다.

  4. 플랫폼 독립성: StateFlow는 Kotlin Multiplatform을 지원하여 다양한 플랫폼에서 사용할 수 있습니다.


MutableLiveData에서 MutableStateFlow로 전환하기


기존의 MutableLiveData 코드를 MutableStateFlow로 전환하는 방법을 단계별로 설명하겠습니다.


기존 코드



kotlin



var _jobWantLists = MutableLiveData<List<JobWantList>>()
val jobWantLists: LiveData<List<JobWantList>> = _jobWantLists




변환된 코드



kotlin



import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow

var _jobWantLists = MutableStateFlow<List<JobWantList>?>(null)
val jobWantLists: StateFlow<List<JobWantList>?> = _jobWantLists




위 코드에서는 MutableLiveData를 MutableStateFlow로 변경하였습니다. MutableStateFlow는 초기 값을 필요로 하므로 null을 초기 값으로 설정했습니다.


StateFlow를 Compose에서 관찰하기


Compose에서 StateFlow를 관찰하려면 collectAsState를 사용할 수 있습니다. 다음은 그 예시입니다.



kotlin



import androidx.compose.runtime.*
import kotlinx.coroutines.flow.collectAsState

@Composable
fun JobWantListScreen(viewModel: JobWantListViewModel) {
val jobWantLists by viewModel.jobWantLists.collectAsState()

jobWantLists?.let {
// jobWantLists 데이터를 사용하여 UI를 구성
} ?: run {
// 데이터가 없을 때의 처리
}
}




위 코드에서는 collectAsState를 사용하여 StateFlow를 Compose에서 상태로 관찰합니다. 그런 다음 jobWantLists 데이터를 사용하여 UI를 구성합니다.


결론


MutableLiveData에서 MutableStateFlow로 전환하면 더 안전하고 유연한 상태 관리를 할 수 있습니다. StateFlow는 Kotlin의 코루틴을 활용하여 더 나은 성능과 확장성을 제공합니다. 여러분도 프로젝트에서 StateFlow를 사용해보세요!





오늘의 이야기

Jetpack Compose와 Retrofit을 활용한 번역기 앱 만들기


translate




1. 프로젝트 설정



먼저, Android Studio에서 새로운 프로젝트를 생성합니다. 그리고 build.gradle 파일에 필요한 종속성을 추가합니다.

Gradle


dependencies {
implementation "androidx.compose.ui:ui:1.0.5"
implementation "androidx.compose.material:material:1.0.5"
implementation "androidx.compose.ui:ui-tooling-preview:1.0.5"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.3.1"
implementation "androidx.activity:activity-compose:1.3.1"
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.1"
}


2. AndroidManifest.xml 설정



인터넷 권한을 추가합니다.
XML


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


3. Retrofit 인터페이스 정의



Retrofit 인터페이스를 정의하여 Google Translate API를 호출하는 방법을 설정합니다.

Kotlin


package com.example.translator

import retrofit2.Call
import retrofit2.http.Body
import retrofit2.http.Headers
import retrofit2.http.POST

interface TranslateService {
@Headers("Content-Type: application/json")
@POST("language/translate/v2")
fun translateText(@Body request: TranslateRequest): Call<TranslateResponse>
}

data class TranslateRequest(
val q: String,
val target: String
)

data class TranslateResponse(
val data: Data
)

data class Data(
val translations: List<Translation>
)

data class Translation(
val translatedText: String
)


4. Retrofit 인스턴스 생성



Retrofit 인스턴스를 생성하고, API 키를 HTTP 헤더에 포함시키기 위해 인터셉터를 설정합니다.

Kotlin


package com.example.translator

import okhttp3.Interceptor
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

object RetrofitInstance {
private const val BASE_URL = "https://translation.googleapis.com/"
private const val API_KEY = "YOUR_API_KEY"

private val client = OkHttpClient.Builder()
.addInterceptor { chain ->
val original = chain.request()
val requestBuilder = original.newBuilder()
.header("Authorization", "Bearer $API_KEY")
val request = requestBuilder.build()
chain.proceed(request)
}
.build()

val api: TranslateService by lazy {
Retrofit.Builder()
.baseUrl(BASE_URL)
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(TranslateService::class.java)
}
}


5. MainActivity.kt 파일 작성



Jetpack Compose를 사용하여 UI를 구성하고, Retrofit을 사용하여 번역 요청을 처리합니다.

Kotlin


package com.example.translator

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
TranslatorApp()
}
}
}

@Composable
fun TranslatorApp() {
var inputText by remember { mutableStateOf("") }
var translatedText by remember { mutableStateOf("") }
val coroutineScope = rememberCoroutineScope()

Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
TextField(
value = inputText,
onValueChange = { inputText = it },
label = { Text("Enter text to translate") },
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(16.dp))
Button(
onClick = {
coroutineScope.launch {
translateText(inputText) { result ->
translatedText = result
}
}
},
modifier = Modifier.fillMaxWidth()
) {
Text("Translate")
}
Spacer(modifier = Modifier.height(16.dp))
Text(text = translatedText)
}
}

fun translateText(text: String, callback: (String) -> Unit) {
val request = TranslateRequest(q = text, target = "es")

RetrofitInstance.api.translateText(request).enqueue(object : Callback<TranslateResponse> {
override fun onResponse(call: Call<TranslateResponse>, response: Response<TranslateResponse>) {
if (response.isSuccessful) {
val translatedText = response.body()?.data?.translations?.firstOrNull()?.translatedText ?: "Translation failed"
callback(translatedText)
} else {
callback("Translation failed: ${response.message()}")
}
}

override fun onFailure(call: Call<TranslateResponse>, t: Throwable) {
callback("Translation failed: ${t.message}")
}
})
}


마무리
이 포스트에서는 Retrofit 인터셉터를 사용하여 Google Translate API 키를 안전하게 전달하는 방법과 Jetpack Compose를 사용하여 간단한 번역기 앱을 만드는 방법에 대해 설명드렸습니다. 이 예제를 기반으로 다양한 기능을 추가하여 더 복잡한 번역기 앱을 만들 수 있습니다. 예를 들어, 다양한 언어를 지원하거나 번역 기록을 저장하는 등의 기능을 추가할 수 있습니다.

이 포스트가 도움이 되셨길 바라며, 더 궁금한 점이 있다면 댓글로 남겨주세요! 행복한 코딩 되세요!





오늘의 이야기


#스하리1000명프로젝트,
Bazen yabancı işçilerle konuşmak zor oluyor, değil mi?
Yardımcı olacak basit bir uygulama yaptım! Siz kendi dilinizde yazarsınız ve başkaları da bunu kendi dillerinde görür.
Ayarlara göre otomatik çeviri yapar.
Kolay sohbetler için son derece kullanışlı. Fırsat bulduğunda bir göz at!
https://play.google.com/store/apps/details?id=com.billcoreatech.multichat416




오늘의 이야기

제트팩 구성에 펄스 효과 생성

펄스...




• 이 기사는 스케일, 알파, 브러시, 모양 및 애니메이션 사양을 제어하는 사용자 정의 수정자를 사용하여 안드로이드용 UI 도구 키트인 제트팩 컴포즈에서 펄스 효과를 생성하는 방법을 보여준다.

• ‘펄스 효과’ 수정자는 ‘리멤버 무한 전환’을 활용해 크기를 초기에서 목표 스케일로 애니메이션화하는 ‘펄스 스케일’과 투명도를 완전히 불투명에서 완전히 투명으로 애니메이션화하는 ‘펄스 알파’의 두 가지 애니메이션을 만들어 모두 무한 반복한다.

• ‘더블 펄스 효과’ 수식어도 제공돼 타이밍이 약간 다르고 색상이 잠재적으로 다른 두 개의 ‘펄스 효과’ 수식어를 레이어드해 시각적으로 더 매력적인 효과를 만들 수 있다.

• 이 기사는 '최소 상호작용 성분 크기' 수정자로 인해 버튼과 같은 상호작용 구성 요소에 펄스 효과를 적용할 때 잠재적인 문제를 강조하며 적절한 렌더링을 위해 구성 요소의 크기를 우선시할 것을 제안한다.

• 여러 예는 '필드아이콘버튼'과 '버튼'과 같은 다양한 제트팩 컴포즈 구성 요소에 적용된 '펄스이펙트'와 '더블펄스이펙트' 수정자를 보여주며 상태 변수를 사용한 옵션 디스플레이를 포함한 다양성과 사용자 정의 옵션을 보여준다.

https://medium.com/@kappdev/how-to-create-a-pulse-effect-in-jetpack-compose-265d49aad044

How to Create a Pulse Effect in Jetpack Compose

Welcome 👋 In this article, we’ll create an engaging Pulse Effect using Jetpack Compose. It’s a great way to capture the user’s attention…

medium.com





오늘의 이야기

안드로이드 개발과 유용한 팁들


 


메리크리스마스



 


안녕하세요, 오늘은 안드로이드 개발에 대한 유용한 팁과 예제를 공유하려고 합니다. 안드로이드 스튜디오 설정부터 자격 증명 관리, 그리고 Jetpack Compose를 활용한 BottomSheet 구현까지 다양한 주제를 다뤄봤습니다.


1. 안드로이드 스튜디오에서 ExampleCustomCredential 설정 방법



gradle



dependencies {
implementation 'com.example:custom-credential:1.0.0'
}




Gradle 파일에 위와 같이 설정을 추가하면 ExampleCustomCredential을 사용할 수 있습니다.


2. Credential Manager를 사용하여 사용자 로그인



java



CredentialManager credentialManager = CredentialManager.getInstance(context);
credentialManager.getCredentialPicker().startCredentialPicker(
CredentialPickerRequest.Builder.forPrompt("Sign In")
.build(),
CredentialRequest.forPassword("com.example.app", "password")
);




Credential Manager를 활용해 사용자를 로그인하는 예제입니다.


3. Jetpack Compose로 BottomSheet 구현 및 하단 고정



kotlin



val sheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden)
val scope = rememberCoroutineScope()

ModalBottomSheetLayout(
sheetState = sheetState,
sheetContent = {
Column(
modifier = Modifier
.fillMaxWidth()
.height(300.dp) // 고정된 높이 설정
.verticalScroll(rememberScrollState()) // 스크롤 가능하게 설정
) {
Text("Item 1")
Text("Item 2")
Text("Item 3")
}
}
) {
// 메인 화면 내용
Button(onClick = {
scope.launch { sheetState.show() }
}) {
Text("Show BottomSheet")
}
}




Jetpack Compose에서 BottomSheet를 구현하고 고정된 높이 내에서 스크롤이 가능하게 설정하는 방법입니다.


4. jQuery를 사용하여 포커스 아웃 감지 및 문자열에서 숫자 추출



javascript



$(document).ready(function() {
$('#myInput').blur(function() {
alert('Input field has lost focus');
});

var str = "abc123def456";
var numbers = str.replace(/\D/g, '');
console.log(numbers); // "123456"
});




jQuery를 사용하여 input 텍스트 필드의 포커스 아웃을 감지하고, 문자열에서 숫자만 추출하는 방법입니다.


5. 여러 .docx 파일 병합 예제 (Java)



java



import org.docx4j.openpackaging.exceptions.Docx4JException;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.docx4j.openpackaging.parts.WordprocessingML.MainDocumentPart;
import org.docx4j.wml.Body;

import java.io.File;

public class DocxMerger {
public static void main(String[] args) throws Docx4JException {
WordprocessingMLPackage doc1 = WordprocessingMLPackage.load(new File("path/to/first.docx"));
MainDocumentPart mainPart1 = doc1.getMainDocumentPart();

WordprocessingMLPackage doc2 = WordprocessingMLPackage.load(new File("path/to/second.docx"));
MainDocumentPart mainPart2 = doc2.getMainDocumentPart();

Body body1 = mainPart1.getJaxbElement().getBody();
Body body2 = mainPart2.getJaxbElement().getBody();

body1.getContent().addAll(body2.getContent());

doc1.save(new File("path/to/merged.docx"));

System.out.println("Document merged successfully!");
}
}




Java의 docx4j 라이브러리를 사용하여 여러 .docx 파일을 하나로 병합하는 방법입니다.


이상으로 오늘의 유용한 안드로이드 개발 팁들을 공유해드렸습니다. 더 궁금한 점이 있으시면 언제든지 질문해 주세요!





오늘의 이야기

2025년에도 다들 행복 하길...

happy new year






오늘의 이야기

#스치니1000프로젝트 #재미 #행운기원 #Compose #Firebase 🎯 야 너 토요일마다 로또 확인하냐? 나도 맨날 “혹시나~” 하면서 봤거든 ㅋㅋ 근데 이제는 그냥 안 해 AI한테 맡겼어 🤖✨ 그것도 구글 Gemini로다가! ...