2026/03/14

오늘의 이야기

onBackPressed 가 deprecated 되었다?


이제 우리는 구글이 제안하는 안드로이드 13에 타기팅하는 앱을 제출 해야만 하는 시기에 도달하고 있습니다.  구글이 새로운 안드로이드 버전을 배포하기 시작하면서 오래된 안드로이드에 대한 게시를 제한 합니다. 


 


그래서 이번에 API 33 인 안드로이드 13에 타겟팅 하는 앱을 작성해 보았습니다. 그러다 만난 몇 가지 사용 제한이 되는 것들에 대한 정리를 해 두고자 합니다. 


 


onBackPressed는 사용자가 뒤로 가기 버튼을 클릭하는 경우 제어를 하기 위해서 사용했던 함수 입니다. MainActivity 에서 최종적으로 뒤로 가기를 클릭 하는 경우 앱을 종료시키는 기능도 사용이 되는 함수였는 데...


 


안드로이드 13에서는 더 이상 사용할 수 없는 (?)  - 사용은 가능 하나 소스 코드에 중간 줄이 생긴 모양을 보면서 코드를 지속적으로 봐야 합니다. 


 


onBackPressed



어떻게 해소를 하면 될까요?


 


CallBack을 하나 만들어 봅니다.


private val callback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
doCloseApps()
}
}

다른 건 없고 이런 모양으로 callback 함수를 하나 만들어 둡니다.  그러고 onCreate 에서 이 callback 이 호출 되도록 한 줄 넣어 주는 것으로 그 코딩은 마무리 됩니다. 


 


@RequiresApi(Build.VERSION_CODES.TIRAMISU)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

this.onBackPressedDispatcher.addCallback(this, callback)


}

그리고 나서 callback에서 처리하고자 하는 코드를 작성하는 것으로 끝입니다. 


 


간단하죠?





오늘의 이야기


#billcorea #운동동아리관리앱
🏸 Schneedle — незаменимое приложение для бадминтонных клубов!
👉 Матчевая игра: записывайте результаты и находите противников 🎉
Идеально подходит для любого места: в одиночку, с друзьями или в клубе! 🤝
Если вам нравится бадминтон, обязательно попробуйте

Зайди в приложение 👉 https://play.google.com/store/apps/details?id=com.billcorea.matchplay




오늘의 이야기

구글 클라우드 함수 설정 


서버 없는 개발자여... 이제 당신도 서버의 역할을 구성할 수 있습니다.  이 글은 아래 개발자 가이드를 참고하여 작성했습니다.


 


https://cloud.google.com/functions/docs/create-deploy-http-python?hl=ko#windows 



 


빠른 시작: Python을 사용하여 HTTP Cloud 함수 만들기 및 배포  |  Cloud Functions 문서  |  Google Cloud


의견 보내기 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 빠른 시작: Python을 사용하여 HTTP Cloud 함수 만들기 및 배포 Python을 사용하여 HTTP Cloud 함수 만들기


cloud.google.com




 


1. 프로젝트 선택


이미 구성해 놓은 여러 개의 프로젝트 중에서 사용할 프로젝트를 선택하는 과정입니다.


프로젝트 선택



 


2. API 사용 설정


다음 해당 프로젝트에서 APIs을 사용할 수 있도록 설정하는 단계입니다.  프로젝트를 확인하고 APIs을 사용하도록 설정합니다.


프로젝트 확인


사용설정



3. gCloud Client을 설치하고 설정을 초기화


설정이 완료되었으니, 이제 cloud sdk installer을 다운로드하고 나서 설치를 진행합니다. 설치하는 과정은 next 버튼을 클릭하는 것으로 완료가 됩니다. 시간은 조금 소요됩니다.


GoogleCloudSDKInstaller.exe 를 다운로드 하고 설치 합니다.

설치가 되고 나면




cloud을 위한 powershell을 찾아서 실행합니다.  (Windows 11 기준에서)


 


4. 환경을 초기화합니다.


이제 환경 설정을 해 보겠습니다. gclound init 실행하면 다음과 같이 환경 설정이 시작됩니다.  여기서 누락된 부분은 서버의 스토리지 위치인데, 가급적이면 asia로 해 주는 것이 나중에 실행 시에 도움이 됩니다. 


PS C:\workspaces\cloudhome\boss0426> gcloud init
Welcome! This command will take you through the configuration of gcloud.

Settings from your current configuration [default] are:
accessibility:
screen_reader: 'False'
core:
account: 6****@gmail.com
disable_usage_reporting: 'False'
project: bespeak-f3bff

Pick configuration to use:
[1] Re-initialize this configuration [default] with new settings
[2] Create a new configuration
Please enter your numeric choice: 2

Enter configuration name. Names start with a lower case letter and contain only lower case letters a-z, digits 0-9, and
hyphens '-': bo****ew
Your current configuration has been set to: [boss0426-new]

You can skip diagnostics next time by using the following flag:
gcloud init --skip-diagnostics

Network diagnostic detects and fixes local network connection issues.
Checking network connection...done.
Reachability Check passed.
Network diagnostic passed (1/1 checks passed).

Choose the account you would like to use to perform operations for this configuration:
[1] 6***@gmail.com
[2] Log in with a new account
Please enter your numeric choice: 1

You are logged in as: [6k2emg@gmail.com].

Pick cloud project to use:
[1] boss0426-f0490
[2] Enter a project ID
[3] Create a new project
Please enter numeric choice or text value (must exactly match list item): 1

Not setting default zone/region (this feature makes it easier to use
[gcloud compute] by setting an appropriate default value for the
--zone and --region flag).
See https://cloud.google.com/compute/docs/gcloud-compute section on how to set
default compute region and zone manually. If you would like [gcloud init] to be
able to do this for you the next time you run it, make sure the
Compute Engine API is enabled for your project on the
https://console.developers.google.com/apis page.

Your Google Cloud SDK is configured and ready to use!

* Commands that require authentication will use 6k***@gmail.com by default
* Commands will reference project `boss0426-f0490` by default
Run `gcloud help config` to learn how to change individual settings

This gcloud configuration is called [boss0426-new]. You can create additional configurations if you work with multiple accounts and/or projects.
Run `gcloud topic configurations` to learn more.

Some things to try next:

* Run `gcloud --help` to see the Cloud Platform services you can interact with. And run `gcloud help COMMAND` to get help on any gcloud command.
* Run `gcloud topic --help` to learn about advanced features of the SDK like arg files and output formatting
* Run `gcloud cheat-sheet` to see a roster of go-to `gcloud` commands.

 


5. python 설정을 시작합니다.


https://cloud.google.com/python/docs/setup?hl=ko 



 


Python 개발 환경 설정  |  Google Cloud


의견 보내기 Python 개발 환경 설정 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 이 가이드에서는 Google Cloud에서 실행되는 Python 앱 개발을 포함하여 Python 개


cloud.google.com




개발 가이드의 내용을 보고 따라 해 봅니다.


먼저 project 가 들어 있는 폴더로 이동합니다.  다음 실행 명령어 다음과 같습니다.



  • py -m pip --version : pip 버전은 항상 최신을 유지하도록 해야 합니다.  python.exe -m pip install --upgrade pip을 실행해서 최신 버전이 설치되도록 한 다음 진행 하면 좋습니다.

  • py -m venv env : 이제 개별 환경을 위한 가상 환경을 구성합니다.

  • .\env\Scripts\actvate : 구성된 가상 환경에서 스크립트를 실행해 화면을 활성화합니다.

  • pip install google-cloud-storage : 가상 환경에 cloud 함수 실행을 위한 라이브러리를 설치합니다.


PS C:\workspaces\cloudhome\boss0426> py -m pip --version
pip 22.3.1 from C:\Users\nari4\AppData\Roaming\Python\Python311\site-packages\pip (python 3.11)
PS C:\workspaces\cloudhome\boss0426> py -m venv env
PS C:\workspaces\cloudhome\boss0426> .\env\Scripts\activate
(env) PS C:\workspaces\cloudhome\boss0426>
(env) PS C:\workspaces\cloudhome\boss0426>
(env) PS C:\workspaces\cloudhome\boss0426>
(env) PS C:\workspaces\cloudhome\boss0426> pip install google-cloud-storage
Collecting google-cloud-storage
Downloading google_cloud_storage-2.7.0-py2.py3-none-any.whl (110 kB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 110.2/110.2 kB 6.7 MB/s eta 0:00:00
Collecting google-auth<3.0dev,>=1.25.0
Downloading google_auth-2.15.0-py2.py3-none-any.whl (177 kB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 177.0/177.0 kB 10.4 MB/s eta 0:00:00
Collecting google-api-core!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.0,<3.0.0dev,>=1.31.5
Downloading google_api_core-2.11.0-py3-none-any.whl (120 kB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 120.3/120.3 kB 6.9 MB/s eta 0:00:00
Collecting google-cloud-core<3.0dev,>=2.3.0
Using cached google_cloud_core-2.3.2-py2.py3-none-any.whl (29 kB)
Collecting google-resumable-media>=2.3.2
Using cached google_resumable_media-2.4.0-py2.py3-none-any.whl (77 kB)
Collecting requests<3.0.0dev,>=2.18.0
Using cached requests-2.28.1-py3-none-any.whl (62 kB)
Collecting googleapis-common-protos<2.0dev,>=1.56.2
Downloading googleapis_common_protos-1.57.0-py2.py3-none-any.whl (217 kB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 218.0/218.0 kB 13.0 MB/s eta 0:00:00
Collecting protobuf!=3.20.0,!=3.20.1,!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<5.0.0dev,>=3.19.5
Downloading protobuf-4.21.12-cp310-abi3-win_amd64.whl (527 kB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 527.0/527.0 kB 11.0 MB/s eta 0:00:00
Collecting cachetools<6.0,>=2.0.0
Using cached cachetools-5.2.0-py3-none-any.whl (9.3 kB)
Collecting pyasn1-modules>=0.2.1
Using cached pyasn1_modules-0.2.8-py2.py3-none-any.whl (155 kB)
Collecting six>=1.9.0
Using cached six-1.16.0-py2.py3-none-any.whl (11 kB)
Collecting rsa<5,>=3.1.4
Using cached rsa-4.9-py3-none-any.whl (34 kB)
Collecting google-crc32c<2.0dev,>=1.0
Using cached google_crc32c-1.5.0-cp311-cp311-win_amd64.whl (27 kB)
Collecting charset-normalizer<3,>=2
Using cached charset_normalizer-2.1.1-py3-none-any.whl (39 kB)
Collecting idna<4,>=2.5
Using cached idna-3.4-py3-none-any.whl (61 kB)
Collecting urllib3<1.27,>=1.21.1
Downloading urllib3-1.26.13-py2.py3-none-any.whl (140 kB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 140.6/140.6 kB 8.1 MB/s eta 0:00:00
Collecting certifi>=2017.4.17
Downloading certifi-2022.12.7-py3-none-any.whl (155 kB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 155.3/155.3 kB ? eta 0:00:00
Collecting pyasn1<0.5.0,>=0.4.6
Using cached pyasn1-0.4.8-py2.py3-none-any.whl (77 kB)
Installing collected packages: pyasn1, urllib3, six, rsa, pyasn1-modules, protobuf, idna, google-crc32c, charset-normalizer, certifi, cachetools, requests, googleapis-common-protos, google-resumable-media, google-auth, google-api-core, google-cloud-core, google-cloud-storage
Successfully installed cachetools-5.2.0 certifi-2022.12.7 charset-normalizer-2.1.1 google-api-core-2.11.0 google-auth-2.15.0 google-cloud-core-2.3.2 google-cloud-storage-2.7.0 google-crc32c-1.5.0 google-resumable-media-2.4.0 googleapis-common-protos-1.57.0 idna-3.4 protobuf-4.21.12 pyasn1-0.4.8 pyasn1-modules-0.2.8 requests-2.28.1 rsa-4.9 six-1.16.0 urllib3-1.26.13

[notice] A new release of pip available: 22.3 -> 22.3.1
[notice] To update, run: python.exe -m pip install --upgrade pip

 


클라우드 함수 만들기 


이제 다시 처음 가이드로 돌아와서 cloud에서 사용할 함수를 만들어 보겠습니다. 저는 python으로 구동하는 함수를 만들기 위해서 환경 설정도 했고 해서 python 으로 돌아가는 함수를 만들 예정입니다. 


 


1. 먼저 python project 폴더를 하나 생성 합니다. (이글에서는 boss0426으로 할 예정입니다.)


2. main.py 코드를 만들고 그 안에 필요한 함수를 구성합니다.  해당 함수의 이름은 deploy 할 때 함수 이름으로 사용되므로 작성 시에 참고하세요. https 호출 시 끝단 URL 이 됩니다.


3. requirements.txt 파일을 하나 작성 해서 import 되어야 하는 항목을 나열해 줍니다. pc에서 python 환경으로 구동할 때는 그냥 source code에 import 하면 실행이 되기는 하지만, cloud 환경에서는 그것이 안 되는 것으로 보입니다. 그래서 text 파일에 모두 기록을 해 주면 실행 시에 load 되어 같이 실행이 되는 것으로 보입니다.


4. 이제 deploy을 해 보겠습니다.


 


    gcloud functions deploy 'function_name' --runtime python310 --trigger-http --allow-unauthenticated


(env) PS C:\workspaces\cloudhome\boss0426> gcloud functions deploy boss0426_request --runtime python310 --trigger-http --allow-unauthenticated
Created .gcloudignore file. See `gcloud topic gcloudignore` for details.
Deploying function (may take a while - up to 2 minutes)...⠛
For Cloud Build Logs, visit: https://console.cloud.google.com/cloud-build/builds;region=us-central1/0d6b660e-85d2-4e6d-86c9-eb09bfaac827?project=319191239543
Deploying function (may take a while - up to 2 minutes)...done.
availableMemoryMb: 256
buildId: 0d6b660e-85d2-4e6d-86c9-eb09bfaac827
buildName: projects/319191239543/locations/us-central1/builds/0d6b660e-85d2-4e6d-86c9-eb09bfaac827
dockerRegistry: CONTAINER_REGISTRY
entryPoint: boss0426_request
httpsTrigger:
securityLevel: SECURE_ALWAYS
url: https://us-c**********0.cloudfunctions.net/boss0426_request
ingressSettings: ALLOW_ALL
labels:
deployment-tool: cli-gcloud
name: projects/boss******0/locations/us-central1/functions/boss0426_request
runtime: python310
serviceAccountEmail: bo*******ppspot.gserviceaccount.com
sourceUploadUrl: https://storage.googleapis.com/uploads-103870777877.us-central1.cloudfunctions.appspot.com/6f47b5ef-c5ef-4163-843d-08c8a48fa10a.zip
status: ACTIVE
timeout: 60s
updateTime: '2023-01-04T05:30:31.359Z'
versionId: '1'
(env) PS C:\workspaces\cloudhome\boss0426>

이제 설정이 완료되었습니다.


 


5. cloud console에서 로그 활동을 확인해 보겠습니다.


콘솔 에서 로그 보기



 


설정도 되었고 호출해보니 호출도 되는 것 같습니다.  이제 실제 앱에서 구동을 해서 확인해 보아야 할 차례입니다.


그 뒷 이야기는 다음에...


 


 





오늘의 이야기

인앱 결제 버전이 올라갔어요



알림: 2022년 8월 2일부터 모든 신규 앱은 결제 라이브러리 버전 4 이상을 사용해야 합니다. 2022년 11월 1일부터는 기존 앱의 모든 업데이트에도 결제 라이브러리 버전 4 이상이 요구됩니다.

구글에서 가이드하는 내용입니다. 이제 구글 인앱 결제로 라이브러리를 업데이트해야 할 것 같아요. 수수료도 30%나 떼어 가면서 요구하는 것도 많습니다. 그래도 어쩌겠어요. 장터(?)가 그것 밖에는 없으니 말이죠.

https://developer.android.com/google/play/billing/migrate-gpblv5?hl=ko







Google Play 결제 라이브러리 4에서 5로의 이전 가이드  |  Google Play 결제 시스템  |  Android Developers


알림: 2022년 8월 2일부터 모든 신규 앱은 결제 라이브러리 버전 4 이상을 사용해야 합니다. 2022년 11월 1일부터는 기존 앱의 모든 업데이트에도 결제 라이브러리 버전 4 이상이 요구됩니다. 자세히


developer.android.com





https://qonversion.io/blog/google-play-billing-library-5-0/







Google Play Billing Library 5.0 overview: new subscription model


Google introduced its new major version of the Google Play Billing Library on recent I/O conference, which include vast information about the new architecture of subscriptions. Let's explore these updates in this article.


qonversion.io





이전 버전에서 사용되던 함수가 더 이상 사용할 수 없게 되면서 이전이 필요해졌습니다. 이전 가이드에 표시되어 있는 것처럼 정리가 일부 필요해 보입니다. 아무튼 이런 정보들을 이용해서 다음 버전으로 이전을 시작해 봅니다.


정기결제 정책도 새로 추가 ?


개발 가이드를 읽어 보면 새로운 API을 활용하기 위해서는 새로운 정기결제 항목이 필요해 보입니다. 새로 등록을 해 보도록 하겠습니다.

정기결제 만들기


콘솔에서 수익 창출 -> 정기 결제에 들어가서 새로운 구독 만들기를 눌러서 새롭게 하나 만들어 봅니다.
다만, 정기결제를 위해서는 앱은 무료로 등록 해야 합니다.

새로 추가한 정기 결제


여기 까지는 쉽게 따라오실 수 있습니다. 그런데 이제는 기본 요금제 및 혜택을 등록하도록 하고 있습니다. 그럼 새로운 기본 요금제를 설정해 보겠습니다.

기본 요금제 선택



  • 자동갱신

  • 선불


2가지의 선택이 있습니다. 자동 갱신은 고객이 해지하기 전까지 자동으로 갱신을 시도한다는 의미이고, 선불은 고객이 필요할 때마다 결제를 진행하여야 하는 경우입니다.

여기서 주의할 점은 선불의 경우는 혜택을 추가할 수 없었습니다. 그래서 추가적인 혜택을 부여하고자 하면 자동 갱신을 만들어 주어야 할 것 같습니다.

** 그리고 저장을 하려고 하면 문제는 출시하려는 국가별로 가격을 정해 주어야 한다는 것입니다. 그래서 처음에는 출시 국가를 줄이는 방법을 선택했다가, 찾아보니 일괄 설정이 가능했습니다.

방법은 화면에 있는 Set prices 클릭하는 것입니다.

전체 일괄 선택후 가격 설정 하기


그러면 국가 리스트가 나오고 일괄 선택도 가능 합니다. 이제 우리나라 통화인 KRW 기준으로 금액을 입력하고 update을 해 보겠습니다.

기준 금액 입력


각 나라별 금액 일괄 입력 후


이제 필요에 따라서 혜택을 추가해 볼 수 있겠습니다. 하지 않아도 되기는 하겠지만 그래도 한번 해 보겠습니다.

정기결제 설정후 상세



혜택추가1단계


id을 입력하고 자격 기준을 선택해 봅니다. 3가지의 선택 사항이 존재하는 데, 이번에는 신규 고객 획득의 경우만 선택해 보겠습니다.



  • 신규고객획득 : 새로 앱을 설치한 사용자에게만 적용됩니다.

  • 업그레이드 : 이전 정기결제를 하고 있는 사용자가 다른 선택을 하게 되는 경우 적용됩니다.

  • 개발자선택 : 개발자 가 선택 사항을 조합해서 적용할 수 있습니다.


단계 추가를 해야 합니다. 신규 고객에게 어떤 조건으로 혜택을 줄 것 인가를 정하게 되는 데, 무료 체험판 제공을 해 보겠습니다.



  • 무료 체험판 : 기간을 정해 놓고 무료 혜택을 제공할 수 있습니다.

  • 1회 결제 : 1회만 혜택을 제공할 수 있습니다.

  • 할인된 반복 결제 : 반복적으로 정해진 기간 동안 할인된 금액으로 결제할 수 있는 혜택을 제공할 수 있습니다.


이제 조건 입력이 되었으니 활성화를 클릭하면 시행됩니다.


앱에서 확인해 보기


이제 앱에서 정상적으로 동작을 하는지 확인해 볼 차례입니다. 음... 등록한 지 얼마 되지 않아서 일까요? 아직 목록이 나오지 않고 있습니다.

동작이 원활하게 되는지 보도록 하겠습니다.





인앱결제 실행 화면


이글과 관련해서 수정된 코드는 아래 github을 참고하세요.

https://github.com/nari4169/daycnt415_kotlin/blob/master/app/src/main/java/com/billcoreatech/daycnt415/billing/BillingManager.kt







GitHub - nari4169/daycnt415_kotlin


Contribute to nari4169/daycnt415_kotlin development by creating an account on GitHub.


github.com





연말이 되었습니다. 2023년에도 열공하는 여러분이 되시길 바랄게요.

이 글이 도움이 되셨다면... 아래 광고도 클릭 한번 부탁드려요. ^^;;





오늘의 이야기


#스하리1000명프로젝트,
迷失在韩国?即使您不会说韩语,这个应用程序也可以帮助您轻松出行。
只需说出您的语言即可 - 它会翻译、搜索并以您的语言显示结果。
非常适合旅行者!支持英语、日语、中文、越南语等10多种语言。
现在就试试吧!
https://play.google.com/store/apps/details?id=com.billcoreatech.opdgang1127




2026/03/13

오늘의 이야기

인앱결제 코드 이전 이야기


이전 버전에서는 java 코드로 구현된 소스 코드를 공유해 보았습니다.


 


https://billcorea.tistory.com/165



 


안드로이드 앱 만들기 : 구글 인앱 결제 쉽게 따라하기 (정기결제, 소스공유)


이전 포스팅 이전에 작성했던 포스팅을 참고하여 인앱 결제를 구현했던 기억을 되살펴 보겠습니다. https://billcorea.tistory.com/27 안드로이드 앱 만들기 구글 인앱결제 쉽게 따라 하기... 인앱 결제를


billcorea.tistory.com




 


오늘은 이 코드를 그대로 kotlin  코드로 변환을 해 보았습니다. 


Java File to Kotlin File


Android Studio 에서는 java 코드를 kotlin으로 변환해 줍니다. 


메뉴에서 Code 제일 아래에 보면 Convert Java File to Kotlin File 이 보입니다.  물로 이 메뉴는 Java 코드일 때만 보입니다.


android studio 메뉴



 


변환을 시행해 보겠습니다. 변환은 내 앱의 상위 package 이름이 나와 있는 위치에서 오른쪽 마우스를 클릭해서 하는 방법도 있습니다.  개발 java 파일을 선택해서 오른쪽 마우스 클릭해서 하게 되는 경우는 개별 파일만 처리해 주지만, 최상위 package을 선택하고 하는 경우 하위 경로에 있는 모든 파일을 한 번에 변환해 줍니다. 


 


주의 사항  


일괄 변환된 후에 해야할 일들이 생깁니다. java 코드에서는 global 변수로 사용하고자 하는 경우 그냥 변수 이름만 선언해 주면 되었던 부분들이 kotlin을 변환하게 되면 그 값을 정해 주는 것에 대해서 설정을 해 주어야 하는 부분들이 생기며  해당 변수를 일괄적으로 null 대입하는 코드로 변환을 해 주시기 때문에 아래 예시들처럼 수정을 해 주어야 하는 부분들이 생깁니다.


 


변환 전 / 후



위 예시는 kotlin  으로 변환 후에 코드를 정리한 후의 코드이니 변환된 직후의 코드와는 다르다는 것을 염두에 두고 보시길 바랍니다. 


 


조금더 자세한 주의 사항을 보시려면 google  에서 제공하는 codelab 을 살펴 보세요.


https://codelabs.developers.google.com/codelabs/java-to-kotlin?hl=ko#1 



 


Kotlin으로 변환  |  Google Codelabs


이 Codelab에서는 자바 코드를 Kotlin으로 변환하는 방법을 알아봅니다.


codelabs.developers.google.com




 


Gradle 설정 추가


 source code 는 변환을 해 주지만, gradle 파일을 자동 변환을 해 주지 않기 때문에 설정을 일부 추가해 주어야 합니다. 


 


먼저 project 의 gradle 파일에는 아래처럼 2곳에 추가를 해 주었습니다.


buildscript {
ext.kotlin_version = '1.7.20' // kotlin 추가
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.3.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // kotlin 추가
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}

 


module의 gradle 파일은 다음과 같이 추가해 주었습니다.


plugins {
id 'com.android.application'
id 'kotlin-android-extensions' // kotlin 추가
id 'kotlin-android' // kotlin 추가
id 'kotlin-kapt' // kotlin 추가
}

 


이제 빌드를 진행해 봅니다. 


 


앱 실행 화면



 


이런 게 변환된 코드는 정상적으로 실행이 되는 것을 확인했습니다. 


 


구글 인앱 결제 


인앱 정기결제 코드는 어떻게 변환이 되었을까요 ?


 



import android.app.Activity
import android.content.Context
import android.content.SharedPreferences
import android.util.Log
import android.widget.Toast
import com.android.billingclient.api.*
import com.billcoreatech.daycnt415.R
import com.billcoreatech.daycnt415.util.KakaoToast
import java.text.SimpleDateFormat
import java.util.*

class BillingManager(var mActivity: Activity) : PurchasesUpdatedListener, ConsumeResponseListener {
var TAG = "BillingManager"
lateinit var mBillingClient: BillingClient
lateinit var mSkuDetails: List<SkuDetails>

enum class connectStatusTypes {
waiting, connected, fail, disconnected
}

var connectStatus = connectStatusTypes.waiting

/**
* 구글에 설정한 구독 상품 아이디와 일치 하지 않으면 오류를 발생 시킴.
* 21.04.20 이번에는 1회성 구매로 변경 210414_monthly_bill_999, 210420_monthly_bill
*/
var punchName = "220302_bill_1month_999"
var punchNameInapp = "210420_monthly_bill"
var payType = BillingClient.SkuType.SUBS
var option: SharedPreferences
var editor: SharedPreferences.Editor

init {
option = mActivity.getSharedPreferences("option", Context.MODE_PRIVATE)
editor = option.edit()
mBillingClient = BillingClient.newBuilder(mActivity)
.setListener(this)
.enablePendingPurchases()
.build()
mBillingClient.startConnection(object : BillingClientStateListener {
override fun onBillingSetupFinished(billingResult: BillingResult) {
Log.e(TAG, "respCode=" + billingResult.responseCode)
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
connectStatus = connectStatusTypes.connected
Log.e(TAG, "connected...")
purchaseAsync()
} else {
connectStatus = connectStatusTypes.fail
Log.i(TAG, "connected... fail ")
}
}

override fun onBillingServiceDisconnected() {
connectStatus = connectStatusTypes.disconnected
Log.i(TAG, "disconnected ")
}
})
}

/**
* 정기 결재 소모 여부를 수신 : 21.04.20 1회성 구매의 경우는 결재하면 끝임.
* @param billingResult
* @param purchaseToken
*/
override fun onConsumeResponse(billingResult: BillingResult, purchaseToken: String) {
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
Log.i(TAG, "사용끝 + $purchaseToken")
return
} else {
Log.i(TAG, "소모에 실패 " + billingResult.responseCode + " 대상 상품 " + purchaseToken)
return
}
}

fun purchase(skuDetails: SkuDetails?): Int {
val flowParams = BillingFlowParams.newBuilder()
.setSkuDetails(skuDetails!!)
.build()
return mBillingClient.launchBillingFlow(mActivity, flowParams).responseCode
}

fun purchaseAsync() {
Log.e(TAG, "--------------------------------------------------------------")
mBillingClient.queryPurchasesAsync(payType) { billingResult, list ->
Log.e(TAG, "onQueryPurchasesResponse=" + billingResult.responseCode)
val sdf = SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
if (list.size < 1) {
editor = option.edit()
editor.putBoolean("isBill", false)
editor.commit()
Log.e(TAG, "getData=" + list.size)
} else {
for (purchase in list) {
Log.e(TAG, "getPurchaseToken=" + purchase.purchaseToken)
for (str in purchase.skus) {
Log.e(TAG, "getSkus=$str")
}
val now = Date()
now.time = purchase.purchaseTime
Log.e(TAG, "getPurchaseTime=" + sdf.format(now))
Log.e(TAG, "getQuantity=" + purchase.quantity)
Log.e(TAG, "getSignature=" + purchase.signature)
Log.e(TAG, "isAutoRenewing=" + purchase.isAutoRenewing)
Log.e(TAG, "getPurchaseState=" + purchase.purchaseState)
editor = option.edit()
editor.putBoolean("isBill", purchase.isAutoRenewing)
editor.commit()
}
}
Log.e(TAG, "--------------------------------------------------------------")
}
}

val skuDetailList: Unit
get() {
val skuIdList: MutableList<String> = ArrayList()
skuIdList.add(punchName)
val params = SkuDetailsParams.newBuilder()
params.setSkusList(skuIdList).setType(payType)
mBillingClient.querySkuDetailsAsync(
params.build(),
SkuDetailsResponseListener { billingResult, skuDetailsList ->
if (billingResult.responseCode != BillingClient.BillingResponseCode.OK) {
Log.i(TAG, "detail respCode=" + billingResult.responseCode)
return@SkuDetailsResponseListener
}
if (skuDetailsList == null) {
KakaoToast.makeToast(
mActivity,
mActivity.getString(R.string.msgNotInfo),
Toast.LENGTH_LONG
).show()
return@SkuDetailsResponseListener
}
Log.i(TAG, "listCount=" + skuDetailsList.size)
for (skuDetails in skuDetailsList) {
Log.i(TAG, """
${skuDetails.sku}
${skuDetails.title}
${skuDetails.price}
${skuDetails.description}
${skuDetails.freeTrialPeriod}
${skuDetails.iconUrl}
${skuDetails.introductoryPrice}
${skuDetails.introductoryPriceAmountMicros}
${skuDetails.originalPrice}
${skuDetails.priceCurrencyCode}
""".trimIndent()
)
}
purchase(skuDetailsList[0])
})
}

/**
* @param billingResult
* @param purchases
*/
override fun onPurchasesUpdated(billingResult: BillingResult, purchases: List<Purchase>?) {
if (billingResult == null) {
Log.wtf(TAG, "onPurchasesUpdated: null BillingResult")
return
}
val responseCode = billingResult.responseCode
val debugMessage = billingResult.debugMessage
Log.d(TAG, "onPurchasesUpdated: ${responseCode} ${debugMessage}")
when (responseCode) {
BillingClient.BillingResponseCode.OK -> if (purchases == null) {
Log.d(TAG, "onPurchasesUpdated: null purchase list")
processPurchases(null)
} else {
processPurchases(purchases)
}
BillingClient.BillingResponseCode.USER_CANCELED -> Log.i(
TAG,
"onPurchasesUpdated: User canceled the purchase"
)
BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED -> Log.i(
TAG,
"onPurchasesUpdated: The user already owns this item"
)
BillingClient.BillingResponseCode.DEVELOPER_ERROR -> Log.e(
TAG, "onPurchasesUpdated: Developer error means that Google Play " +
"does not recognize the configuration. If you are just getting started, " +
"make sure you have configured the application correctly in the " +
"Google Play Console. The SKU product ID must match and the APK you " +
"are using must be signed with release keys."
)
}
}

private fun processPurchases(purchasesList: List<Purchase>?) {
if (purchasesList != null) {
Log.d(TAG, "processPurchases: " + purchasesList.size + " purchase(s)")
} else {
Log.d(TAG, "processPurchases: with no purchases")
}
if (isUnchangedPurchaseList(purchasesList)) {
Log.d(TAG, "processPurchases: Purchase list has not changed")
return
}
}

/**
* subs 의 경우는 아래와 같이 구매확인을 해 주어야 됨.
* @param purchase
*/
fun confirmPerchase(purchase: Purchase) {
//PURCHASED
if (purchase.purchaseState == Purchase.PurchaseState.PURCHASED) {
if (!purchase.isAcknowledged) {
val acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder()
.setPurchaseToken(purchase.purchaseToken)
.build()
mBillingClient.acknowledgePurchase(acknowledgePurchaseParams) { billingResult ->
Log.e(TAG, "getResponseCode=" + billingResult.responseCode)
editor.putBoolean("isBill", true)
editor.commit()
}
}
} else if (purchase.purchaseState == Purchase.PurchaseState.PENDING) {
//구매 유예
Log.e(TAG, "//구매 유예")
} else {
//구매확정 취소됨(기타 다양한 사유...)
Log.e(TAG, "//구매확정 취소됨(기타 다양한 사유...)")
}
}

private fun isUnchangedPurchaseList(purchasesList: List<Purchase>?): Boolean {
for (purchase in purchasesList!!) {
confirmPerchase(purchase)
}
return false
}
}

이전에 포스팅했던 java 코드와 비교를 해 보면 코드가 많이 간소화되었다는 것을 알 수 있습니다.  호출해서 사용하는 코드는 github의 코드를 참고해 보세요. 


 


결제 테스트


결제 진행에 대한 테스트는 꼭 playstore에 게시한 이후에 진행하여야 합니다.  저는 내부 테스트로 게시한 이후 진행 하고 있습니다. 그리고  정기결제 항목을 처음 등록한 경우에는 해당 결제 항목이 사용이 될 수 있으려면 24시간 이상 걸리는 경우가 있으므로 미리 정기결제 항목을 등록해 두고 앱을 만들어 가는 것이 시간 활용에 도움이 됩니다. 


 


결제 테스트



 


이상으로 Java 코드의 소스를 Kotlin으로 변환을 해 보았습니다.


 


전체 코드 보기


전체소스코드는 아래 링크를 참고하세요.


https://github.com/nari4169/daycnt415_kotlin



 


GitHub - nari4169/daycnt415_kotlin


Contribute to nari4169/daycnt415_kotlin development by creating an account on GitHub.


github.com




 





오늘의 이야기

정기결제


앱에 결제 기능을 다는 이야기는 이전 포스팅에 있습니다. 이번에는 혜택을 주는 방법에 대한 이야기를 적어 봅니다. 


먼저 이전에 등록해서 운영하던 경우를 기준으로 작성하고 있음을 밝혀 둡니다.  이전에 만들었던 앱에 매월 정기 결제를 통해 광고를 제거하는 옵션을 달았던 적이 있습니다. 


 


정기 결제(구독)이 등록된 정보



 


그중에서 현재 운영 중인 구독 보기를 선택합니다.


 


혜택 추가 하기



 


혜택 추가


이제 혜택 추가를 해 보겠습니다.


 


혜택 추가



혜택 추가 하기에는 신규 고객을 선택하는 경우와 이전 사용자를 선택 하는 경우, 그 외 개발자의 임의 지정을 선택할 수 있을 듯합니다.  기존 고객을 위한 프로모션을 하는 경우도 있겠지만, 제가 배포한 앱은 아직 사용자가 없기 때문에 신규 고객을 대상으로 한 혜택 추가를 해 보겠습니다.


자격기준



저 선택 사항 아래 탱크를 달도록 되어 있는 데, 일단은 무시해 보겠습니다.


저장해 보기



 


그냥 저장 버튼을 눌렀더니 아래와 같이 단계를 추가하도록 가이드를 하고 있습니다.  단계는 2개까지 등록이 될 것 같습니다.


 


단계 추가



 


신규 고객에게 혜택을 등록하는 것으로 정했으니, 단계 추가에서는 무료 체험판이라고 선택을 하는 것이 맞을 듯합니다. 


 



  • 무료체험판  : 지정하는 기간 동안 무료 체험을 제공합니다.

  • 1회 결제      : 지정하는 기간 동안 1회 결제에 한하여 정액, 할인율, 일정금액 등으로 가격을 조정해 줄 수 있습니다. 

  • 할인된 반복 결제 : 지정하는 결제 기간 동안 정액, 할인율, 일정금액 등으로 가격을 조정해 줄 수 있습니다.


단계 옵션



저는 신규 고객에게 3개월 동안 무료 체험을 할 수 있도록 하고자 합니다. 그랬더니 판매가 되는 국가별로 가격표가 노출이 됩니다. 그리고 적용을 눌러보겠습니다.


무료체험 단계



적용하고 활성화를 시켜 봅니다. 


 


혜택 활성화 예시



결제 진행


내부 테스트 계정으로 앱을 설치하고 테스트를 진행해 봅니다. 


 


인앱 결제 혜택 추가 후 결제 처리 화면



 


이상으로 정기 구독자를 위한 혜택 추가 하는 방법에 대한 이야기를 추가해 봅니다.


 





오늘의 이야기



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

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

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

그것도 구글 Gemini로다가!

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

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

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


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




오늘의 이야기

AVD


Android Virtual Device는 Android Studio에서 앱을 개발하고 테스트하는 동안 실물 기기를 대신해서 테스트해 볼 수 있도록 지원하는 장치(?)입니다. 가상의 휴대폰이 되는 것입니다.

https://developer.android.com/studio/run/managing-avds?hl=ko







가상 기기 만들기 및 관리하기  |  Android 개발자  |  Android Developers


Android 스튜디오에서 가상 기기를 만들고 관리하는 방법에 관해 알아보세요.


developer.android.com





앱을 개발 하다 보면 카메라 촬영을 통해서 이미지를 사용하는 앱들도 구현하게 됩니다. 이런 경우 AVD에서 직접 촬영한 이미지를 볼 수 있도록 하면 좋을 것 같습니다. (이미 알고 계시는 경우도 있기는 하겠지만...)


설정해 보기


Android Studio 을 실행하고 AVD을 하나 실행해 보겠습니다. Android Studio Dolphin | 2021.3.1 Patch 1을 기준으로 설명해 드립니다. 이전 버전에서도 지원이 되기는 하니 참고하시면 될 것 같습니다.

먼저 오른쪽 바에서 Device Manager 을 열어서 이미 만들어 놓은 가상 머신 하나를 선택하고 연필 모양의 수정 버튼을 크릭 합니다.

device manager



이제 설정 화면이 나오면 왼쪽 하단에 있는 show Advanced Settings을 클릭해서 열어 봅니다.

설정 화면


Camera 설정을 보면 Front 카메라에는 없지만, Back 카메라에는 VirtualScene 라는 옵션이 있습니다. 이 옵션이 선택되도록 수정하고 저장을 클릭합니다.

선택 사항은 다음과 같습니다.



  • None : 카메라가 없는 선택

  • VirtualScene : 시뮬레이션 환경에서 가상 카메라 사용

  • Emulated : 시뮬레이션 카메라 사용

  • Device : 호스트 컴퓨터 웹캠 또는 내장 카메라 사용



가상 카메라는 마치 카메라가 있는 것 처럼 가상공간을 촬영하는 시뮬레이션을 해 준다는 의미입니다. 이제 앱을 실행해서 어떤 동작을 하는지 살펴보겠습니다.


이미지 적용 방법



  • avd 을 실행합니다. 다음 오른쪽 제일 하단에 있는 햄버거 메뉴를 클릭하여 설정 화면으로 들어갑니다. camera 메뉴를 클릭합니다.

  • Virtual Sence Images 에는 wall과 table 두 개가 있는데 wall 은 벽면에 표시되는 이미지 이고 table 은 테이블 위에 표시 되는 이미지 을 설정 하는 부분입니다. 이미지 열기를 선택하여 보여 주고 싶은 이미지를 선택 하여 적용합니다.

  • 주의할 것은 이미지 촬영 시 휴대폰이 세로가 되도록 해서 촬영된 이미지를 적용해 주세요. 그래야 아래 예시처럼 벽면에 정상적으로 된 이미지를 보여 줄 수 있습니다.

이미지 적용 순서




  • 적용된 이미지는 가상공간에서 주방을 찾아가면 벽면과 테이블에 각각 선택한 이미지가 보입니다.





카메라 영상 샘플


이제 AVD에서 카메라를 열어 선택한 이미지가 나오는 곳을 찾아보도록 하겠습니다.


가상공간 탐색


AVD의 카메라에 보이는 가상공간을 탐색해 보겠습니다. AVD의 하단에 보면 alt 키를 이용하여 카메라를 이동할 수 있다는 표시가 나옵니다.

alt 키와 w (전진), s (후진), a (왼쪽으로), d (오른쪽으로) 등의 방향키를 이용하여 카메라의 시선을 옮겨 봅니다. 잘 찾아보면 거실에 있는 TV을 비추고 있던 카메라 안에 강아지 모양도 보이고, 주방으로 가는 공간도 보일 겁니다. 위 동영상 예시와 같이 말입니다.

alt 키를 누른 상태에서 마우스를 드래그해 보면 카메라의 앵글이 움직이는 것도 보실 수 있습니다.

AVD 카메라 모습



이와 같이 해서 주방 뒤에 있는 다이닝 룸(?)을 찾아가셨다면 벽면에 비치는 사진과 테이블 위에 비치는 사진을 보실 수 있습니다. 이렇게 해서 AVD에서 이미지 보여 주기 방법에 대하여 알아보았습니다.





오늘의 이야기

소셜 로그인 firebase에서 지원하고 있는 소셜 로그인(?)은 Google, Facebook, Apple, Microsoft, Twitter 등 대부분 외국계(?)입니다. firebase 의 소셜 로그인 지원 우리나라에서 대다수가 사용하는 nave...