2026/03/08

오늘의 이야기

about Android Studio



개발 툴에 패치가 있네요... 다른 건 아직이고 testOption에 대한 부분이 설명이 있어서 한번 읽어 볼꼐요.. 


 


Gradle Managed Virtual Devices (그래들 관리 가상 머신?)


In order to improve consistency, performance, and reliability when using Android Virtual Devices for your automated instrumented tests, we're introducing Gradle Managed Virtual Devices. This feature allows you to configure virtual test devices in your project's Gradle files that the build system uses to fully manage those devices—that is, create, deploy, and tear down—to execute your automated tests.
 
You can specify a virtual device that you want Gradle to use for testing your app in your module-level build.gradle file. The following code sample creates a Pixel 2 running API level 30 as a Gradle managed device.


 


자동화된 계측 테스트에 Android 가상 장치를 사용할 때 일관성, 성능 및 안정성을 개선하기 위해 Gradle 관리 가상 장치를 도입합니다. 이 기능을 사용하면 빌드 시스템이 자동화된 테스트를 실행하기 위해 해당 장치를 완전히 관리(즉, 생성, 배포 및 해체)하는 데 사용하는 프로젝트의 Gradle 파일에서 가상 테스트 장치를 구성할 수 있습니다.
 
모듈 수준 build.gradle 파일에서 Gradle이 앱 테스트에 사용할 가상 기기를 지정할 수 있습니다. 다음 코드 샘플은 API 레벨 30을 실행하는 Pixel 2를 Gradle 관리 기기로 생성합니다.


 


--- 아마도 테스트와 관련된 패치인 것으로 보이지만... 아직이라. 확인이 되면 다시 수정해 보도록 하겠습니다.





오늘의 이야기

앱을 만들다 보면 지도가 들어가는 앱을 만들게 되는 경우가 있다. GoogleMap API 등을 이용해서 앱을 만들게 되는 데, 이번에는 Jetpack Compose 기반의 GoogleMap 을 구현해 볼까 한다.


 


googleMap 들어 있는 앱(개발중...)



아직은 예전 java 코드에서 kotlin 코드로 이전 작업을 하는 중이라 미완성의 모습으로 보이는 데, 일단, 앱에 구글맵을 넣어 보았다.


 


먼저 gradle 파일에 추가해야 하는 것들은 다음과 같다.


// compose Maps
implementation 'com.google.android.gms:play-services-maps:18.1.0'
implementation "com.google.accompanist:accompanist-permissions:0.26.0-alpha"
implementation 'com.google.maps.android:maps-compose:2.5.3'
implementation "androidx.compose.foundation:foundation:$compose_version"
implementation 'com.google.maps.android:maps-compose-widgets:2.5.3'

버전에 따라서 달라질 수 는 있지만, 오늘 현재는 위와 같이 설정해 주면 된다. 


 


다음은 manifast 파일에 권한 설정을 보겠다.


 


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

일반적으로는 background_location 은 잘 사용하지 않는다, 구글에서도 background_location 권한을 사용하는 경우에는 사용자에게 권한 사용을 하고 있다는 안내를 통해서 권한 획득을 해야 한다고 하고 있고, fine_location 권한까지는 릴리즈 하는 동안에도 그다지 크게 통제(?)를 하지는 않지만, background_location 권한은 playstore에 릴리즈 하는 동안에도 까다롭게 심사를 하는 편이다.


 


이제 화면을 Jetpack compose 을 통해서 구현할 것이니 코드를 보도록 하겠다.



@OptIn(ExperimentalPermissionsApi::class)
@Composable
fun MapsScreen (
navigator: DestinationsNavigator,
) {

// 기본위치 표시을 위해 싱가폴의 위치정보
val singapore = LatLng(1.35, 103.87)
// 지도의 크기 조정에 따른 상태 기록
val cameraPositionState = rememberCameraPositionState {
position = CameraPosition.fromLatLngZoom(singapore, 10f)
}
// 구글맵 환경설정 관리
var uiSettings by remember { mutableStateOf(MapUiSettings(zoomControlsEnabled = sp.getBoolean("isMyLocationEnabled", false))) }
// 구글맵의 형태등을 관리 MayType.NORMAL 일반적인 맵, SATELLITE 위성사진, HYBRID 일반과 위성사진의 혼합등등 설정
var properties by remember {
mutableStateOf(MapProperties(mapType = MapType.NORMAL, isMyLocationEnabled = sp.getBoolean("isMyLocationEnabled", false)))
}
// 권한 획득에 관한 설정
val permissionState = rememberPermissionState(
permission = Manifest.permission.ACCESS_FINE_LOCATION,
)

....

when (permissionState.status) {
PermissionStatus.Granted -> {
Box(Modifier.fillMaxWidth().height(screenHeight * .3f)) {
GoogleMap(
modifier = Modifier.matchParentSize(),
cameraPositionState = cameraPositionState,
properties = properties,
uiSettings = uiSettings,
) {
Marker(
state = MarkerState(position = singapore),
title = "Singapore",
snippet = "Marker in Singapore"
)
}
Switch(
checked = uiSettings.zoomControlsEnabled,
onCheckedChange = {
uiSettings = uiSettings.copy(zoomControlsEnabled = it)
properties = properties.copy(isMyLocationEnabled = it)
}
)
}
}
else -> {
navigator.navigate(HomeScreenDestination)
}
}
}
}

 


대략적인 구글맵을 사용한 코드는 위와 같이 구현이 될 수 있다.  좀 더 자세한 것은 아래 링크를 참고해서 다시 봐야 할 듯 하지만...


 


https://developers.google.com/maps/documentation/android-sdk/maps-compose



 


지도 Compose 라이브러리  |  Android용 Maps SDK  |  Google Developers


의견 보내기 지도 Compose 라이브러리 Jetpack Compose는 UI 개발을 간소화하고 가속화하는 선언적인 네이티브 UI 도구 모음입니다. Jetpack Compose를 사용하면 앱이 어떻게 표시될지 설명하고 Jetpack Compose


developers.google.com




 


이제 실행 되는 모습을 잠시 살펴보면서 마무리를 해야겠다.


 



실행 동영상


 





오늘의 이야기


#스하리1000명프로젝트,
In Korea verloren? Auch wenn Sie kein Koreanisch sprechen, hilft Ihnen diese App dabei, sich problemlos fortzubewegen.
Sprechen Sie einfach Ihre Sprache – es übersetzt, sucht und zeigt Ergebnisse in Ihrer Sprache an.
Ideal für Reisende! Unterstützt mehr als 10 Sprachen, darunter Englisch, Japanisch, Chinesisch, Vietnamesisch und mehr.
Probieren Sie es jetzt aus!
https://play.google.com/store/apps/details?id=com.billcoreatech.opdgang1127




2026/03/07

오늘의 이야기

 




해가 저무는  바다는
이 더위를 식혀줄 요량인지 바람이  살랑 거린다.
하루 종일 이글 거리던 햇살을 이제 감추어야 하는 시간이 되어 바라보던 바닷가에

아이가 바다를 바라본다. 무엇을 남기고 싶은 건가?
이제 긴 여름도 입추라는 계절의 변화에 아직 버티고 있는 건지?

아직도 한낮의 기온은 30도를 넘나 든다.  입추는  언제쯤이나 실감을 하게 될지...

바다가 보이는 바위틈에서 서서...
오늘도 하루를 잘 보냈으니, 다가올 내일도 나에겐 행복한 기운만 함께 하길 바라본다.





오늘의 이야기

parametermap으로 String []을 전달해서 select 처리할 때 변수로 사용해 보고자 했다. 


흑~ 값이 전달 되지 않는다... 이유는 무엇인가?  이틀 무렵이나 구글 신(?)에서 질문을 했으나, 답이 찾아지지 않는다.


 


그런던 이튼날 답을 찾았다. ㅋ~


 


String[] aString 

HashMap <String, object> paramenterMap = new HaspMap<>();
paramemterMap.put("stringArr", aString);

...

파라미터 전달은 이 정도...


 


xml에서 sql은 어떻게?


 


<select id="select" resultType="resultVo" parameterType="map">
select * from table_name
where key in
<foreach collection="stringArr" item="item" open="(" close=")" separator=",">
'${item}'
</foreach>

</select>

이렇게 코딩하면 끝...


 


이전에는 #{item}이라고 코딩하면 될 꺼라 믿었고... 구글 신(?)이 알려주는 해법(?)들도 많은 글들이 그렇게 되어 있었지만...


 


결국에는 '${item}' 이 답이라는 결론(?)을 얻었다.  ㅠㅠ;





오늘의 이야기



#스치니1000프로젝트 #재미 #행운기원 #Compose #Firebase

🎯 야 너 토요일마다 로또 확인하냐?
나도 맨날 "혹시나~" 하면서 봤거든 ㅋㅋ

근데 이제는 그냥 안 해
AI한테 맡겼어 🤖✨

그것도 구글 Gemini로다가!

그래서 앱 하나 만들었지
👉 "로또 예상번호 by Gemini" 🎱

AI가 분석해서 번호 딱! 뽑아줌
그냥 보고 참고만 하면 됨

재미로 해도 좋고…
혹시 모르는 거잖아? 😏


https://play.google.com/store/apps/details?id=com.billcorea.gptlotto1127




오늘의 이야기

https://vtsen.hashnode.dev/free-android-development-learning-resources-for-beginners



 


Free Android Development Learning Resources for Beginners


Ultimate free resources and courses that I find useful while learning Android development as a beginner


vtsen.hashnode.dev




 


오늘은 아침에 찾은 링크 하나를 공유하는 것으로...


 


짧은 영어 이기는 해도 배우는 것들이 다 영문 사이트 이므로... 한 번은 읽어봐야 할 것 같아서...


 


끝.


 


링크이미지






오늘의 이야기


#스하리1000명프로젝트

스치니들!
내가 만든 이 앱은, 내 폰에 오는 알림 중에서 중요한 키워드가 있는 경우
등록해둔 친구에게 자동으로 전달해주는 앱이야 📲

예를 들어, 카드 결제 알림을 와이프나 자녀에게 보내주거나
이번 달 지출을 달력처럼 확인할 수도 있어!

앱을 함께 쓰려면 친구도 설치 & 로그인해줘야 해.
그래야 친구 목록에서 서로 선택할 수 있으니까~
서로 써보고 불편한 점 있으면 알려줘 🙏

👉 https://play.google.com/store/apps/details?id=com.nari.notify2kakao





오늘의 이야기

앱에서 알림을 구현하는 방법은 여러 가지가 있다. alertDiaglog을 이용하는 방법도 있기는 하지만, 요새는 SnackBar을 구현하는 경우가 많은 것 같다. Jetpack Compose에서는 아직 잘 모르겠는 부분이 있어서 구글을 하다 찾아보게 되었다.


 


https://stackoverflow.com/questions/68909340/how-to-show-snackbar-with-a-button-onclick-in-jetpack-compose



 


How to show snackbar with a button onclick in Jetpack Compose


I want to show snackbar with a button onclick in Jetpack Compose I tried this Button(onClick = { Snackbar(action = {}) { Text("hello") } } But AS said "@Composable


stackoverflow.com




 


이제 실전으로 갈 볼까 ? 먼저 위 글에서 퍼온 코드를 일부 수정해서 공통적으로 사용할 수 있도록 준비를 해 보아야겠다.


 


import android.annotation.SuppressLint
import android.util.Log
import androidx.compose.material.Scaffold
import androidx.compose.material.SnackbarResult
import androidx.compose.material.rememberScaffoldState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import kotlinx.coroutines.launch

@SuppressLint("UnusedMaterialScaffoldPaddingParameter", "CoroutineCreationDuringComposition")
@Composable
fun SnackBarShow(
message:String, actionLabel: String,
doDismissed:() -> Unit,
doActionPerformed:() -> Unit
) {
val scaffoldState = rememberScaffoldState() // this contains the `SnackbarHostState`
val coroutineScope = rememberCoroutineScope()

Scaffold(
modifier = Modifier,
scaffoldState = scaffoldState // attaching `scaffoldState` to the `Scaffold`
) {
coroutineScope.launch {
// using the `coroutineScope` to `launch` showing the snackbar
// taking the `snackbarHostState` from the attached `scaffoldState`
val snackbarResult = scaffoldState.snackbarHostState.showSnackbar(
message = message,
actionLabel = actionLabel
)
when (snackbarResult) {
SnackbarResult.Dismissed -> {
Log.e("SnackBarShow", "Dismissed")
doDismissed()
}
SnackbarResult.ActionPerformed -> {
Log.e("SnackBarShow", "SnackBar's button clicked")
doActionPerformed()
}
}
}
}
}

코드를 일부 동작할 수 있도록 수정했다.  @SuppressLint("UnusedMaterialScaffoldPaddingParameter", "CoroutineCreationDuringComposition")는 코드에서 오류 표시가 나는 것 때문에 수정을 하기는 했다. Padding Parameter을 사용하지 않는 것과 DuringComposition 부분인 것 같기는 하지만, 아직은 잘 모른다.


 


그리고 doDismissed(), doActionPerformed() 함수는 화면의 버튼을 클릭 했을 때 동작을 처리하는 부분을 구현해야 하기 때문에 return 될 함수의 선언으로 추가해 주었다.


 


이제 MainActivity 에서 코드를 구현해 보아야겠다.


 


setContent{
SnackBarShow(
getString(R.string.msgFinish), getString(R.string.titleFinish),
doDismissed = {},
doActionPerformed = {
finish()
}
)
}

어디에서는 간에 setCotent 로 감싸고 나서 위에서 작성한 SnackBarShow을 호출해 주는 것이다. 파라미터는 메시지로 보여줄 것과 버튼에 들어갈 문구를 전달하고 return 되는 함수는 SnackBar의 버튼을 클릭하지 않은 경우 처리와 , 버튼을 클릭했을 때 사용할 처리를 위해서 구현한 함수 2개를 돌려받았다.


 


실행이미지



 





오늘의 이야기

https://flatteredwithflutter.com/using-compose-destinations%ef%bf%bc/



 


Using compose destinations


We will cover briefly: Current navigation in composeUsing compose destinations(Optional) Modify existing test cases Current navigation in compose We get Compose Navigation from the Jetpack Com…


flatteredwithflutter.com




navigation 의 첫 이야기 다음... 그것을 어떻게 풀어낼 것인가를 찾아 돌아다니다가 또 하나의 링크를 찾았다. 이것을 보면서 이해를 하기 시작해 본다. 


 


그래서 오늘은 따라해 보기를 해 보아야겠다.


 


먼저 build gradle 에 설정을 따라해 본다. 


 


plugins {
...
id 'com.google.devtools.ksp' version '1.7.0-1.0.6'
}

1.7.0-1.0.6 은 코틀린 버전과 연계가 되어야 하는 버전을 맞추어 주는 것으로 이해를 하였다. 코틀린이 1.7.10까지 패치가 되어 가는 것 같기는 하지만, 일단 확인된 바로는 1.7.0 까지 인 것 같아서...


 


android {

...

applicationVariants.all { variant ->
kotlin.sourceSets {
getByName(variant.name) {
kotlin.srcDir("build/generated/ksp/${variant.name}/kotlin")
}
}
}

kotlin.sourceSets.all {
languageSettings.optIn("kotlin.RequiresOptIn")
}

...

}

이 부분이 들어가면 gradle 빌드를 통해서 kotlin 이라는 폴더가 생기면서 필요한 class 등을 만들어 주는 경로를 설정하는 것으로 이해가 되었다.


 


dependencies {

...
// compose destination
// https://github.com/raamcosta/compose-destinations 에서 최종 버전을 확인
implementation 'io.github.raamcosta.compose-destinations:animations-core:1.7.15-beta'
ksp 'io.github.raamcosta.compose-destinations:ksp:1.7.15-beta'
implementation "androidx.hilt:hilt-navigation-compose:1.0.0"

}

다음은 implementation  을 선언해 주는 것인데, 버전 확인은 원작자의 github 에서 확인하여 수정하면 최신 버전이 사용될 것 같다. 


 


다음은 activity 을 만들어 주어야 하는 데... 지금은 테스트 하는 것이기 때문에 간단하게 수정해 보았다.


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

doUpdateCheck()

setContent {

MainTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = softBlue
) {
// github 에서 본 것 처럼 추가.
val navHostEngine = rememberAnimatedNavHostEngine()
DestinationsNavHost(navGraph = NavGraphs.root, engine = navHostEngine)

}
}
}
}

이렇게 추가를 해 주고 나면 NavGraphs 가 생성이 되지 않아서 오류 표시가 나오지만, 일단은 무시하고 화면을 구성할 부분을 만들어 주었다.


 


@Destination(start = true)
@Composable
fun LoginScreen(
navigator: DestinationsNavigator
) {
Card(modifier = Modifier.fillMaxSize()) {
Text(text = stringResource(id = R.string.AppId))
}
Button(onClick = {
navigator.navigate(HomeScreenDestination)
}) {
Text(text = stringResource(id = R.string.action_geoList))
}

}

@Destination
@Composable
fun HomeScreen(
navigator: DestinationsNavigator
) {
/*...*/
Button(
onClick = {
navigator.navigate(
ProfileScreenDestination(
id = "someId",
isEditable = true
)
)
}
) {
Text(text = stringResource(id = R.string.action_addItem))

}
}

@Destination
@Composable
fun ProfileScreen(
navigator: DestinationsNavigator,
id: String,
isEditable: Boolean = false
) {
Button (onClick = {
navigator.popBackStack()
navigator.navigate(SearchScreenDestination("Text"))
}) {
Text(text = stringResource(id = R.string.action_Setting))
}
}

@Destination
@Composable
fun SearchScreen(
navigator: DestinationsNavigator,
query: String?
) {
Button (onClick = {
navigator.navigate(HomeScreenDestination)
}) {
Text(text = stringResource(id = R.string.action_setHome))
}
}

github 에서 보았던 예제를 참고해서 만들었고 동작 확인을 위해서 버튼만 추가해서 처리가 되는 지 확인해 보았다.


 


그 다음은 빌드를 위한 준비를 해 보자. android studio 의 terminal 에서 command 창을 열어서 


 


gradlew 실행



./gradlew clean build 을 입력해서 실행해 주면 gradle 을 실행 되면서 필요한 빌드를 하게 된다. 


 


생성된 폴더 와 class들



실행이 완료 되면 kotlin 폴더 아래와 위 그림과 같이 generated 된 파일들이 생성이 되고 이제 정말 앱을 빌드할 준비가 된다. 이제 나의 앱을 build 해서 실행해 보면 된다. 


 


https://billcorea.tistory.com/205



 


안드로이드 앱 만들기 : Compose Navigation ... 인터넷 펌.


https://medium.com/@cybercoder.naj/compose-navigation-in-3-minutes-5cff3c57c34e Compose Navigation in 3 Minutes Quick guide for navigation between composables in a Compose project medium.com 나름대..


billcorea.tistory.com




이렇게 실행해 보면 navigation 선언등등 이전 포스팅에서 적었던 것 같은 NavigationItem 등등 선언하지 않아도 navigation 을 구현할 수 있으므로 추가 하거나 할 때 다른 작업들을 잊어 버려도 오류가 나지 않을 것 같다.   (기능등을 비교해 보면 좋을 것 같다.)


 


즐~ 코딩 하길 바라며... 이걸 만든 원작자에게 감사의 인사를 보낸다...





오늘의 이야기


#스하리1000명프로젝트,
A veces es difícil hablar con trabajadores extranjeros, ¿verdad?
¡Hice una aplicación sencilla que ayuda! Escribes en tu idioma y los demás lo ven en el suyo.
Se traduce automáticamente según la configuración.
Súper útil para chatear fácilmente. ¡Echa un vistazo cuando tengas la oportunidad!
https://play.google.com/store/apps/details?id=com.billcoreatech.multichat416




오늘의 이야기

구글에서는 이제 android 13을 대상으로 앱을 게시하기 위해서는 광고 ID을 선언해야 한다고 합니다.  물론 이전부터(2022.4.1 이후) 적용되기 시작했던 부분이나, 9월이 된 이제 서야 준비를 해 봅니다. 그동안은 API 버전이 32 이하인...