2026/04/18

오늘의 이야기


#billcorea #운동동아리관리앱
🏸スチーニーたち、バドミントン同好会必須アプリ登場!
👉マッチプレイ - スコア記録&試合相手を探す🎉
一人で、友達、同好会どこでもぴったりです! 🤝
バドミントン好きならぜひ使ってみてください

アプリショートカット👉 https://play.google.com/store/apps/details?id=com.billcorea.matchplay




오늘의 이야기

MS 코파일럿, 월 200달러 챗GPT 고급 AI 기능 무료 제공





• 마이크로소프트(MS)가 자사의 코파일럿에 오픈AI의 고급 AI 모델인 o1을 활용한 '씽크 디퍼' 기능을 무료로 제공한다고 발표했습니다. 기존에 챗GPT 프로에서 월 200달러에 제공되던 o1 모델을 코파일럿 사용자라면 누구나 무료로 이용할 수 있게 함으로써, 고성능 AI 접근성을 대폭 높였습니다.

• 씽크 디퍼는 몇 초간 분석 및 검색을 거쳐 심층적인 답변을 제공하며, 기후 변화 분석, 역사적 사건 해석, 프로그래밍 코드 작성 등 복잡한 작업에도 강점을 보입니다.  이는 단순하고 짧은 답변에 집중했던 기존 코파일럿과는 다른 접근 방식이며, 2023년 10월까지의 데이터를 기반으로 동작합니다.

• 현재 MS는 씽크 디퍼의 유료화 계획을 밝히지 않았으나,  오픈AI가 더욱 발전된 o3 모델을 출시한 점을 고려할 때, AI 업계의 빠른 기술 발전 속도와 향후 서비스 정책 변화 가능성을 주목해야 합니다.

https://www.cio.com/article/3813934/ms-%EC%9B%94-200%EB%8B%AC%EB%9F%AC-%EC%B1%97gpt-%EA%B3%A0%EA%B8%89-ai-%EA%B8%B0%EB%8A%A5-%EC%9C%88%EB%8F%84%EC%9A%B0-%EC%82%AC%EC%9A%A9%EC%9E%90%EC%97%90-%EB%AC%B4%EB%A3%8C-%EC%A0%9C.html?amp=1

MS “월 200달러 챗GPT 고급 AI 기능, 윈도우 사용자에 무료 제공”

MS가 코파일럿의 새로운 ‘씽크 디퍼(Think Deeper)’ 기능을 통해 오픈AI의 o1 모델을 무료로 제공한다.

www.cio.com





오늘의 이야기


 




jQuery 배열 복사 방법


행운이 함께 하길 기원 드려요



  1. slice() 메서드 사용:


  2. javascript



    var originalArray = [1, 2, 3, 4];
    var copiedArray = originalArray.slice();




  3. concat() 메서드 사용:


  4. javascript



    var originalArray = [1, 2, 3, 4];
    var copiedArray = [].concat(originalArray);




  5. Array.from() 메서드 사용:


  6. javascript



    var originalArray = [1, 2, 3, 4];
    var copiedArray = Array.from(originalArray);




  7. 스프레드 연산자 사용 (ES6):


  8. javascript



    var originalArray = [1, 2, 3, 4];
    var copiedArray = [...originalArray];





jQuery를 사용한 배열 요소 이름과 갯수 확인



javascript



var originalArray = ['apple', 'banana', 'cherry', 'date'];

// 요소 이름 출력
originalArray.forEach(function(element) {
console.log(element);
});

// 요소 갯수 출력
console.log('Array length:', originalArray.length);




jQuery를 사용한 객체 배열의 이름과 값 확인



javascript



var originalArray = [
{ name: 'apple', color: 'red' },
{ name: 'banana', color: 'yellow' },
{ name: 'cherry', color: 'red' },
{ name: 'date', color: 'brown' }
];

// 객체 배열의 이름과 값 출력
originalArray.forEach(function(element) {
console.log('Name: ' + element.name + ', Color: ' + element.color);
});




객체 배열의 속성 이름을 모를 때



javascript



var originalArray = [
{ name: 'apple', color: 'red' },
{ name: 'banana', color: 'yellow' },
{ name: 'cherry', color: 'red' },
{ name: 'date', color: 'brown' }
];

// 모든 키와 값 출력
originalArray.forEach(function(element) {
Object.keys(element).forEach(function(key) {
console.log(key + ': ' + element[key]);
});
console.log('---'); // 각 객체 사이에 구분선 추가
});









오늘의 이야기

안녕하세요! 오늘은 Jetpack Compose와 com.afollestad.material-dialogs 라이브러리를 사용하여 비밀번호 입력 다이얼로그를 구현하는 방법에 대해 알아보겠습니다. Jetpack Compose는 최신 Android UI 툴킷으로, 선언형 프로그래밍 모델을 사용하여 UI를 효율적으로 구축할 수 있습니다. com.afollestad.material-dialogs는 다양한 유형의 다이얼로그를 쉽게 만들 수 있게 해주는 라이브러리입니다.


 


비밀번호 입력



 


사전 준비


먼저, 프로젝트의 build.gradle 파일에 다음 의존성을 추가해야 합니다.


 

dependencies {
implementation "com.afollestad.material-dialogs:core:3.3.0"
implementation "com.afollestad.material-dialogs:input:3.3.0"
}

위의 의존성을 추가한 후, Gradle을 동기화(Sync)합니다.


비밀번호 입력 다이얼로그 구현


이제 Jetpack Compose에서 com.afollestad.material-dialogs 라이브러리를 사용하여 비밀번호 입력 다이얼로그를 구현하는 방법을 단계별로 살펴보겠습니다.


1. MainActivity 설정


먼저 MainActivity를 설정합니다. Jetpack Compose의 setContent를 사용하여 UI를 설정합니다.


 

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import com.example.myapp.ui.theme.MyAppTheme

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

2. PasswordDialogExample 컴포저블


PasswordDialogExample 컴포저블은 버튼을 클릭할 때 비밀번호 입력 다이얼로그를 표시합니다. 다이얼로그가 닫히면 입력된 비밀번호를 표시합니다.


 

@Composable
fun PasswordDialogExample() {
var showDialog by remember { mutableStateOf(false) }
var password by remember { mutableStateOf("") }

if (showDialog) {
PasswordInputDialog(
onDismiss = { showDialog = false },
onPasswordEntered = { enteredPassword ->
password = enteredPassword
showDialog = false
}
)
}

Button(onClick = { showDialog = true }) {
Text("Show Password Dialog")
}

if (password.isNotEmpty()) {
Text("Entered Password: $password")
}
}

3. PasswordInputDialog 컴포저블


PasswordInputDialog 컴포저블은 Material Dialogs를 사용하여 비밀번호 입력 다이얼로그를 생성합니다. 비밀번호 입력이 완료되면 onPasswordEntered 콜백을 호출하고, 다이얼로그가 닫힙니다.


 

@Composable
fun PasswordInputDialog(
onDismiss: () -> Unit,
onPasswordEntered: (String) -> Unit
) {
val context = LocalContext.current
LaunchedEffect(Unit) {
MaterialDialog(context).show {
title(text = "Enter Password")
input(
hint = "Password",
inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD
) { dialog, text ->
onPasswordEntered(text.toString())
}
positiveButton(text = "Submit") {
it.dismiss()
}
negativeButton(text = "Cancel") {
it.dismiss()
}
lifecycleOwner(LocalLifecycleOwner.current)
}.setOnDismissListener {
onDismiss()
}
}
}

4. Xml Layout의 구성


input 을 사용하는 경우에는  라이브러리 제공자의 github 에서 참조 하여 xml layout 을 복사해 주어야 합니다.  이유는 사용하는  라이브러리가 xml layout 을 기반으로 하고 있어서 그런 부분이 있습니다.  자세한 내용은 원작자의 github 을 참고 하세요.


 


material-dialogs/documentation/INPUT.md at main · afollestad/material-dialogs



 


material-dialogs/documentation/INPUT.md at main · afollestad/material-dialogs


😍 A beautiful, fluid, and extensible dialogs API for Kotlin & Android. - afollestad/material-dialogs


github.com




 


<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="@dimen/md_dialog_frame_margin_horizontal"
android:paddingRight="@dimen/md_dialog_frame_margin_horizontal"
android:paddingBottom="@dimen/md_dialog_frame_margin_vertical_less">

<com.google.android.material.textfield.TextInputLayout
android:id="@+id/md_input_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content">

<com.google.android.material.textfield.TextInputEditText
android:id="@+id/md_input_message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:text="Hello, World!" />
</com.google.android.material.textfield.TextInputLayout>

</FrameLayout>

 


5.  해당 Activity 의 theme 수정 


 위에서 말한 xml layout 을 활용 하기 위해서는 사용하는 activitify 의 테마 정보도 제약이 있습니다.  아래 내용으로 지정 하는 않으면 build 하는 과정에서 오류 메시지가 나타나게 됩니다.   아래의 예시에서 참고할 부분은 parent theme 로 지정된 Theme.AppCompat 으로 지정하는 않으면 build 과정에서 오류가 발생 됩니다. 그리고 manifest 파일에서 해당 activity 의  theme 도 아래 name 의 값으로 지정해 주어야 합니다.


 


<?xml version="1.0" encoding="utf-8"?>
<resources>

<style name="Theme.Multichat416" parent="Theme.AppCompat" >
<item name="fontFamily">@font/notosanskr_regular</item>
<item name="md_color_title">@color/white</item>
<item name="md_color_content">@color/white</item>
<item name="md_color_button_text">@color/white</item>
<item name="md_background_color">@color/softBlue</item>
<item name="md_ripple_color">@color/white</item>
<item name="md_divider_color">@color/softBlue</item>
<item name="md_font_button">@color/softRed</item>
<item name="md_item_selector">@color/softBlue</item>

</style>
</resources>

마무리


이렇게 해서 Jetpack Compose와 com.afollestad.material-dialogs 라이브러리를 사용하여 비밀번호 입력 다이얼로그를 구현하는 방법에 대해 알아보았습니다. 이 예제는 기본적인 다이얼로그 구현 방법을 보여주며, 필요에 따라 다이얼로그의 디자인과 동작을 커스터마이즈할 수 있습니다.


이 글이 도움이 되었기를 바랍니다! 질문이나 의견이 있으시면 댓글로 남겨주세요. 감사합니다!





오늘의 이야기

컬러링북 판매 일주일에 500달러 벌기

컬러링 북



• 저자 클라이라 타이드는 750+의 독특하고 맞춤형 디자인이 담긴 자원인 '수익형 컬러링 페이지 PLR 팩'을 이용해 만든 컬러링 북을 판매해 일주일 만에 500달러를 벌어들인 경험을 자세히 설명한다.

• 이 PLR 팩은 교육적인 테마를 가진 고품질의 만화 스타일의 일러스트를 제공했으며 칸바를 사용하여 쉽게 편집할 수 있어 독특한 색칠책을 빠르게 만들고 브랜딩할 수 있습니다.

• 타이드는 디자인을 맞춤화한 다음 Etsy와 Amazon KDP와 같은 플랫폼에서 결과적인 색칠책을 판매하여 붐을 일으키는 어린이 교육 시장의 참여 및 교육 자원에 대한 수요를 활용하여 자원을 활용했습니다.

• 타이드는 지속적인 노력으로 월 2,000달러의 잠재적 수익을 예상하고 성인용 컬러링 북을 포함하도록 제품 라인을 확장하고 추가 판매 플랫폼을 탐색할 계획이며, 이 창의적인 분야에서 상당한 소득 창출 가능성을 강조한다.

https://medium.com/@ClyraTide/how-i-made-500-in-one-week-selling-coloring-books-0a25767b8315

How I Made $500 in One Week Selling Coloring Books

Earning through creative outlets has never been easier.

medium.com





오늘의 이야기

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를 사용해보세요!





오늘의 이야기

#billcorea #운동동아리관리앱 🏸スチーニーたち、バドミントン同好会必須アプリ登場! 👉マッチプレイ - スコア記録&試合相手を探す🎉 一人で、友達、同好会どこでもぴったりです! 🤝 バドミントン好きならぜひ使ってみてください アプリショートカット...