소스트리 사용 중 파일 변경 확인시에 내가 변경한 라인까지 보이지 않는 현상이 있었다.
이를 간단히 해결해보자
아래의 이미지에서 최대 비교 줄 수 옵션을 기본 500 에서 1000으로 늘려주니 수정사항에 대해서 모두 확인할 수 있었음!
소스트리 사용 중 파일 변경 확인시에 내가 변경한 라인까지 보이지 않는 현상이 있었다.
이를 간단히 해결해보자
아래의 이미지에서 최대 비교 줄 수 옵션을 기본 500 에서 1000으로 늘려주니 수정사항에 대해서 모두 확인할 수 있었음!
현재 진행하는 사이드 프로젝트 작업 중 데이터 클래스에 대해서 파일을 하나만 선언했는데에도 gradle 빌드 중 redeclaration 오류가 발생하는 현상이 있었다.
Windows 환경에서는 잘 동작하지만 Mac 환경에서는 항상 발생하는 것으로 봐서 환경적인 문제로 생각되어 Invalidate cache 및 Restart 시도 해봤지만 동일하게 발생하여 구글링 한 결과 해결할 수 있었다.
gradle 캐시를 지운 후 정상적으로 동작했다.
./gradlew clean
rm -rf ~/.gradle/caches/*
Mac 또는 Linux 환경에서 권한이 없다고 아래처럼 터미널 창에 표시 될 경우에는 chmod 명령어를 통해 권한을 주자.
zsh: permission denied: ./gradlew
여튼 위의 명령어를 통해 오류를 해결할 수 있었다!!
Reference :
https://stackoverflow.com/questions/56795138/kotlin-file-changes-result-in-redeclaration-error
Kotlin file changes result in Redeclaration error
Every change to a kotlin file results in a Redeclaration error when building. The only way around it is by cleaning the project and then rebuilding. There are no other files in the project with the...
stackoverflow.com
경화는 과수원에서 귤을 수확했습니다. 경화는 수확한 귤 중 'k'개를 골라 상자 하나에 담아 판매하려고 합니다. 그런데 수확한 귤의 크기가 일정하지 않아 보기에 좋지 않다고 생각한 경화는 귤을 크기별로 분류했을 때 서로 다른 종류의 수를 최소화하고 싶습니다.
예를 들어, 경화가 수확한 귤 8개의 크기가 [1, 3, 2, 5, 4, 5, 2, 3] 이라고 합시다. 경화가 귤 6개를 판매하고 싶다면, 크기가 1, 4인 귤을 제외한 여섯 개의 귤을 상자에 담으면, 귤의 크기의 종류가 2, 3, 5로 총 3가지가 되며 이때가 서로 다른 종류가 최소일 때입니다.
경화가 한 상자에 담으려는 귤의 개수 k와 귤의 크기를 담은 배열 tangerine이 매개변수로 주어집니다. 경화가 귤 k개를 고를 때 크기가 서로 다른 종류의 수의 최솟값을 return 하도록 solution 함수를 작성해주세요.
6 | [1, 3, 2, 5, 4, 5, 2, 3] | 3 |
4 | [1, 3, 2, 5, 4, 5, 2, 3] | 2 |
2 | [1, 1, 1, 1, 2, 2, 2, 3] | 1 |
입출력 예 #1
입출력 예 #2
입출력 예 #3
class Solution {
fun solution(k: Int, tangerine: IntArray): Int {
var answer: Int = 0
var ans_k = k
var list: MutableList<Int> = ArrayList() // 귤 갯수를 담음
var set: Set<Int> = tangerine.toSet() // 귤 종류를 담음
for(i in set) {
val cnt = tangerine.count { it == i} // 귤 종류에서 중복되는 갯수를 리스트에 담기
list.add(cnt)
}
list.sortDescending() // 갯수가 많은순으로 정렬
for (i in list) {
if (ans_k <= 0 )
return answer
ans_k -= i // 판매할만큼 빼주기
answer++
}
return answer
}
}
코틀린이랑 친해지기 위해서 문제를 풀어봤다.. 종종 풀어봐야징
[백준] 2002번 추월 자바 풀이 (0) | 2023.07.25 |
---|---|
[백준] 14400번 편의점 2 자바 풀이 (0) | 2023.07.25 |
[프로그래머스] 위클리 챌린지 - 직업군 추천하기 (0) | 2021.08.28 |
[프로그래머스] 위클리 챌린지 - 상호 평가 Kotlin 풀이 (0) | 2021.08.12 |
[프로그래머스] 위클리 챌린지 - 부족한 금액 계산하기 (0) | 2021.08.03 |
개발 도중 manifestPlaceholders를 통해 카카오 앱 키를 local.properties 파일에서 읽어오는 기능이 필요했는데 매니페스트에서 읽을 때에만 정상적으로 읽지 못해서 카카오맵이 정상적으로 보이지 않는 이슈가 있었다.
원인은 매우 간단했는데, local.properties에서 선언한 앱키를 따옴표 없이 감싸준 이후 정상동작 했다.
아래는 참고 할만한 코드를 공유 해본다
// build.gradle
Properties properties = new Properties()
properties.load(project.rootProject.file('local.properties').newDataInputStream())
def KAKAO_API_KEY = properties.getProperty("kakao_api_key")
android {
...
defaultConfig {
...
manifestPlaceholders = [KAKAO_API_KEY:KAKAO_API_KEY]
}
...
}
// local.properties
kakao_api_key=abcde.... // 여기서 큰 따옴표(" "), 작은 따옴표(' ')로 감싸면 안됨!!
// android.manifest
<meta-data
android:name="com.kakao.sdk.AppKey"
android:value="${KAKAO_API_KEY}" /> // 요러케 사용
나처럼 헤맨 사람이 있다면 얼른 해결하시길 ㅠ
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.hyeonwoo.art_android, PID: 26068
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.hyeonwoo.art_android/com.hyeonwoo.art_android.ui.main.MainActivity}: android.view.InflateException: Binary XML file line #35 in com.hyeonwoo.art_android:layout/activity_main: Binary XML file line #35 in com.hyeonwoo.art_android:layout/activity_main: Error inflating class com.google.android.material.bottomnavigation.BottomNavigationView
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3654)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3806)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:83)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2267)
at android.os.Handler.dispatchMessage(Handler.java:107)
at android.os.Looper.loop(Looper.java:237)
at android.app.ActivityThread.main(ActivityThread.java:8167)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:496)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1100)
Caused by: android.view.InflateException: Binary XML file line #35 in com.hyeonwoo.art_android:layout/activity_main: Binary XML file line #35 in com.hyeonwoo.art_android:layout/activity_main: Error inflating class com.google.android.material.bottomnavigation.BottomNavigationView
Caused by: android.view.InflateException: Binary XML file line #35 in com.hyeonwoo.art_android:layout/activity_main: Error inflating class com.google.android.material.bottomnavigation.BottomNavigationView
Caused by: java.lang.reflect.InvocationTargetException
at java.lang.reflect.Constructor.newInstance0(Native Method)
at java.lang.reflect.Constructor.newInstance(Constructor.java:343)
at android.view.LayoutInflater.createView(LayoutInflater.java:854)
at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:1006)
at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:961)
at android.view.LayoutInflater.rInflate(LayoutInflater.java:1123)
at android.view.LayoutInflater.rInflateChildren(LayoutInflater.java:1084)
at android.view.LayoutInflater.inflate(LayoutInflater.java:682)
at android.view.LayoutInflater.inflate(LayoutInflater.java:534)
at android.view.LayoutInflater.inflate(LayoutInflater.java:481)
at androidx.appcompat.app.AppCompatDelegateImpl.setContentView(AppCompatDelegateImpl.java:710)
at androidx.appcompat.app.AppCompatActivity.setContentView(AppCompatActivity.java:195)
at androidx.databinding.DataBindingUtil.setContentView(DataBindingUtil.java:303)
at androidx.databinding.DataBindingUtil.setContentView(DataBindingUtil.java:284)
at com.hyeonwoo.art_android.ui.BaseActivity.onCreate(BaseActivity.kt:15)
at com.hyeonwoo.art_android.ui.main.MainActivity.onCreate(MainActivity.kt:16)
at android.app.Activity.performCreate(Activity.java:7963)
at android.app.Activity.performCreate(Activity.java:7952)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1307)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3629)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3806)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:83)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2267)
at android.os.Handler.dispatchMessage(Handler.java:107)
at android.os.Looper.loop(Looper.java:237)
at android.app.ActivityThread.main(ActivityThread.java:8167)
at java.lang.reflect.Method.invoke(Native Method)
E/AndroidRuntime: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:496)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1100)
Caused by: android.content.res.Resources$NotFoundException: Resource ID #0x7f0601ea
at android.content.res.ResourcesImpl.getValue(ResourcesImpl.java:276)
at android.content.res.Resources.getValue(Resources.java:1452)
at androidx.core.content.res.ResourcesCompat.isColorInt(ResourcesCompat.java:309)
at androidx.core.content.res.ResourcesCompat.inflateColorStateList(ResourcesCompat.java:256)
at androidx.core.content.res.ResourcesCompat.getColorStateList(ResourcesCompat.java:236)
at androidx.core.content.ContextCompat.getColorStateList(ContextCompat.java:519)
at androidx.appcompat.content.res.AppCompatResources.getColorStateList(AppCompatResources.java:48)
at androidx.appcompat.widget.TintTypedArray.getColorStateList(TintTypedArray.java:179)
at com.google.android.material.navigation.NavigationBarView.<init>(NavigationBarView.java:168)
at com.google.android.material.bottomnavigation.BottomNavigationView.<init>(BottomNavigationView.java:108)
at com.google.android.material.bottomnavigation.BottomNavigationView.<init>(BottomNavigationView.java:103)
at com.google.android.material.bottomnavigation.BottomNavigationView.<init>(BottomNavigationView.java:98)
... 31 more
I/Process: Sending signal. PID: 26068 SIG: 9
AVD 장치에서는 오류가 없었는데, 실제 장치에서 실행 시 activity를 띄우는 도중 크래시가 발생했다 ㅠ
정확한 원인은 잘 모르겠지만, 오류 로그를 통해 문제가 발생하는 지점이 BottomNavigationView 라는걸 알수있었음!
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/main_bottom_navigation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:itemBackground="@color/white"
app:itemIconTint="@color/material_dynamic_tertiary50"
app:itemTextColor="@color/black"
app:menu="@menu/menus" />
수상해 보이는 건 메뉴 아니면 머테리얼 컬러인것 같아서 컬러 값을 변경해보니 정상 동작했다...
해당 material color의 실제 색상 Hex값으로 수정 후 실행하니 크래시가 발생하지 않았음 ~
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/main_bottom_navigation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#CAC1EA"
app:itemBackground="@color/white"
app:itemIconTint="#FF787296"
app:itemTextColor="@color/black"
app:menu="@menu/menus" />
더 좋은 방법있음 댓글 달아주쇼~
최근 개발중인 사이드 프로젝트 앱에서 안드로이드 스튜디오를 통해 앱을 실행할 경우, 실행은 되지만 앱이 설치가 되지않는 현상이 있었다.
해당 이슈의 원인은 바로! AndroidManifest 파일에 있었는데
<activity
android:name=".ui.main.MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data
android:host="oauth"
android:scheme="kakao"/>
</intent-filter>
<meta-data
android:name="android.app.lib_name"
android:value="" />
</activity>
위의 인텐트 필터를 보면 MAIN, LAUNCHER와 함께 여러 값들이 있는데, MAIN, LAUNCHER에는 데이터 태그를 처리할 수 없다고 한다... 그래서 아래처럼 인텐트 필터를 분리시켜주면 된다고 한다.
<activity
android:name=".ui.main.MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE"/>
<category android:name="android.intent.category.DEFAULT"/>
<data
android:host="oauth"
android:scheme="kakao"/>
</intent-filter>
<meta-data
android:name="android.app.lib_name"
android:value="" />
</activity>
이제 다시 설치해보자!!
정상적으로 앱이 설치된걸 확인했다.
Reference :
https://stackoverflow.com/questions/20285496/android-application-installed-but-wont-open-on-device
Android Application Installed but won't open on device
I created an application on Android. I am developing it on eclipse with ADT. It is about nfc. For the moment it only reads and writes tag. I run my application on my mobile device for testing and...
stackoverflow.com
Hilt를 사용하면서 만난 오류이다.
error: [Dagger/MissingBinding] @dagger.hilt.android.qualifiers.ActivityContext android.content.Context cannot be provided without an @Provides-annotated method. public abstract static class SingletonC implements ArtOnApplication_GeneratedInjector,
에러가 난 코드는 아래와 같다.
class KakaoAuthService @Inject constructor(
@ActivityContext private val context: Context
) {
fun startKakaoLogin(kakaoLoginCallback: (OAuthToken?, Throwable?) -> Unit) {
val kakaoLoginState = if (UserApiClient.instance.isKakaoTalkLoginAvailable(context)) {
KAKAO_TALK_LOGIN
} else {
KAKAO_ACCOUNT_LOGIN
}
when (kakaoLoginState) {
KAKAO_TALK_LOGIN -> UserApiClient.instance.loginWithKakaoTalk(
context,
callback = kakaoLoginCallback
)
KAKAO_ACCOUNT_LOGIN -> UserApiClient.instance.loginWithKakaoAccount(
context,
callback = kakaoLoginCallback
)
}
}
companion object {
const val KAKAO_TALK_LOGIN = 0
const val KAKAO_ACCOUNT_LOGIN = 1
}
}
ActivityContext를 사용하는 부분에서 오류가 난 것인데, ActivityContext의 경우 @Provide 를이용해서 의존성을 주입하라는 것 같다. Stack Overflow를 통해 비슷한 오류를 찾아봤는데
@HiltViewModelViewModel이 구성 변경의 활동보다 오래 지속되기 때문에 누출이 발생할 수 있으므로 활동 컨텍스트(또는 활동 자체)를 삽입할 수 없습니다. (구글 번역기..)
대략 ActivityContext의 생명주기 보다, ViewModel의 생명주기가 더 길기 때문에 의존성 주입이 불가능하다.. 라고 하는 것 같다.
나의 경우 ActivityContext가 아닌, ApplicationContext를 이용해도 상관없어 보여서 ApplicationContext을 주입하도록 코드를 수정해주니 정상적으로 빌드되었다.
class KakaoAuthService @Inject constructor(
@ApplicationContext private val context: Context
)
다른 좋은 방법이 있다면 공유 부탁드립니다..😜
Error inflating class com.google.android.material.bottomnavigation.BottomNavigationView (0) | 2022.11.13 |
---|---|
android studio에서 실행한 앱이 설치되지 않을 때 (0) | 2022.11.06 |
Android WebView 테스트 샘플 앱 개발 - 2 (0) | 2022.10.22 |
Android WebView 샘플 앱 개발 - 1 (0) | 2022.10.22 |
Android에서 Firebase Cloud Messaging 테스트 앱 개발 - 1 (0) | 2022.10.19 |
이전시간에는 Android WebView를 통해 당근마켓 홈페이지를 표시하는 기능을 개발했습니다.
그러나 WebView 또다른 url로 이동하게 될 경우 WebView 내부가 아닌, 외부 브라우저를 통해 앱이 페이지가 표시되는 이슈가 있었습니다. 이를 해결해봅니다.
Android Developer 페이지의 WebView 문서에서 이 문제를 간단하게 해결할 수 있는 방법을 제시합니다.
사용자가 WebView에서 웹페이지의 링크를 클릭하면 URL을 처리하는 앱이 Android에서 실행되는 것이 기본 동작입니다. 대개 기본 웹브라우저에 도착 URL이 열리고 로드됩니다.
하지만 링크가 WebView내에서 열리도록 WebView의 이 동작을 재정의할 수 있습니다. 그러면 WebView에 의해 유지 관리되는 웹페이지 방문 기록을 통해 사용자가 앞뒤로 탐색할 수 있습니다.
webView 객체에 아래의 코드를 추가하여 사용자가 클릭한 링크가 WebView 내에서 로드되게 할 수 있습니다.
webView.webViewClient = WebViewClient() // 사용자가 클릭한 모든 링크가 WebView에 로드되게 함.
또한, 문서 바로아래에 뒤로가기 버튼을 클릭했을 때 WebView에서 이전 페이지를 표시하는 기능도 제공합니다.
MainActivity내에 아래의 코드를 추가해줍니다.
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
// Check if the key event was the Back button and if there's history
if (keyCode == KeyEvent.KEYCODE_BACK && webView.canGoBack()) {
webView.goBack()
return true
}
// If it wasn't the Back key or there's no web page history, bubble up to the default
// system behavior (probably exit the activity)
return super.onKeyDown(keyCode, event)
}
이로서 간단하게 WebView 내에서 페이지를 이동하고, 뒤로가기 버튼 클릭시 WebView 내에서 뒤로가기로 동작하는 기능을 추가 했습니다.
reference :
https://developer.android.com/guide/webapps/webview?hl=ko#HandlingNavigation
WebView에서 웹 앱 빌드 | Android 개발자 | Android Developers
WebView에서 웹 앱 빌드 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 웹 애플리케이션 또는 웹페이지만 클라이언트 애플리케이션의 일부로 제공하려는 경
developer.android.com
현재 project내 front-end개발 인원에 비해 Android 앱 개발 인원이 부족하여 안드로이드 앱 네이티브 기능이 필요하지 않은 부분은 협의하에 WebView로 구현하여 프로젝트 일정에 부담을 덜어내기로 합니다.
웹 애플리케이션 또는 웹페이지만 클라이언트 애플리케이션의 일부로 제공하려는 경우 [WebView](<https://developer.android.com/reference/android/webkit/WebView?hl=ko>)를 사용하면 됩니다. [WebView](<https://developer.android.com/reference/android/webkit/WebView?hl=ko>) 클래스는 Android의 [View](<https://developer.android.com/reference/android/view/View?hl=ko>)클래스의 확장으로, 웹페이지를 활동 레이아웃의 일부로 표시할 수 있게 해 줍니다. 탐색 컨트롤이나 주소 표시줄 등 완전히 개발된 웹브라우저의 기능은 전혀 포함되어 있지 않습니다.
[WebView](<https://developer.android.com/reference/android/webkit/WebView?hl=ko>)의 모든 작업은 기본적으로 웹페이지를 표시하는 것입니다.
[WebView](<https://developer.android.com/reference/android/webkit/WebView?hl=ko>)를 앱에 추가하려면 활동 레이아웃에서 <WebView> 요소를 포함하거나 onCreate() 에서 전체 활동 창을 WebView로 설정하면 됩니다.
AndroidManifest.xml 파일의 manifest 태그 내에 아래와 같이 추가합니다.
<uses-permission android:name="android.permission.INTERNET" />
MainActivity에는 webView를 로드합니다. 테스트로 당근마켓의 홈페이지를 로드했습니다.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val webView = findViewById(R.id.webview)
webView.loadUrl("<https://www.daangn.com/>")
}
}
activity_main.xml 파일에는 WebView를 추가합니다.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<WebView
android:id="@+id/webview"
android:layout_width="match_parent"
android:layout_height="match_parent">
</WebView>
</androidx.constraintlayout.widget.ConstraintLayout>
여기까지 설정 시 아래처럼 안드로이드 앱 실행 시 당근마켓의 홈 페이지를 표시합니다. 그러나 당웹뷰 내의 버튼을 클릭 시 앱 내에서 이동하지 않고 새로운 chrome 앱을 통해 페이지를 표시했습니다.
이번 포스팅에서는 웹뷰를 표시하는것 까지 확인하고, 다음 포스팅에서 앱 내에서 어떻게 웹 페이지를 이동할 수 있는지 알아보겠습니다.
지난시간에서 우리는 firebase 콘솔에서 FCM을 사용하기 위한 세팅을 완료했습니다.
이번시간에는 Android 앱에서 어떻게 푸쉬 알림을 수신하고 메세지를 표시하는지 알아봅시다.
먼저 firebase 메세지를 수신하기 위해 MyFirebaseMessagingService 클래스를 생성하고 아래와 같이 코드를 붙여넣습니다. 소스코드는 firebase 공식 github의 소스코드를 이용했습니다.
class MyFirebaseMessagingService : FirebaseMessagingService() {
/**
* Called when message is received.
*
* @param remoteMessage Object representing the message received from Firebase Cloud Messaging.
*/
// [START receive_message]
// 메세지 수신 function
override fun onMessageReceived(remoteMessage: RemoteMessage) {
// [START_EXCLUDE]
// There are two types of messages data messages and notification messages. Data messages are handled
// here in onMessageReceived whether the app is in the foreground or background. Data messages are the type
// traditionally used with GCM. Notification messages are only received here in onMessageReceived when the app
// is in the foreground. When the app is in the background an automatically generated notification is displayed.
// When the user taps on the notification they are returned to the app. Messages containing both notification
// and data payloads are treated as notification messages. The Firebase console always sends notification
// messages. For more see: <https://firebase.google.com/docs/cloud-messaging/concept-options>
// [END_EXCLUDE]
// TODO(developer): Handle FCM messages here.
// Not getting messages here? See why this may be: <https://goo.gl/39bRNJ>
Log.d(TAG, "From: ${remoteMessage.from}")
// Check if message contains a data payload.
// firebase-cloud-message 알림 작성시 추가 옵션에 설정한 맞춤 데이터를 수신.
if (remoteMessage.data.isNotEmpty()) {
Log.d(TAG, "Message data payload: ${remoteMessage.data}")
if (/* Check if data needs to be processed by long running job */ true) {
// For long-running tasks (10 seconds or more) use WorkManager.
scheduleJob()
} else {
// Handle message within 10 seconds
handleNow()
}
}
// Check if message contains a notification payload.
remoteMessage.notification?.let {
Log.d(TAG, "Message Notification Body: ${it.body}")
it.body?.let { body -> sendNotification(body) }
}
// Also if you intend on generating your own notifications as a result of a received FCM
// message, here is where that should be initiated. See sendNotification method below.
}
// [END receive_message]
// [START on_new_token]
/**
* Called if the FCM registration token is updated. This may occur if the security of
* the previous token had been compromised. Note that this is called when the
* FCM registration token is initially generated so this is where you would retrieve the token.
*/
override fun onDeletedMessages() {
super.onDeletedMessages()
}
override fun onNewToken(token: String) {
Log.d(TAG, "Refreshed token: $token")
// If you want to send messages to this application instance or
// manage this apps subscriptions on the server side, send the
// FCM registration token to your app server.
sendRegistrationToServer(token)
}
// [END on_new_token]
/**
* Schedule async work using WorkManager.
*/
private fun scheduleJob() {
// [START dispatch_job]
val work = OneTimeWorkRequest.Builder(MyWorker::class.java).build()
WorkManager.getInstance(this).beginWith(work).enqueue()
// [END dispatch_job]
}
/**
* Handle time allotted to BroadcastReceivers.
*/
private fun handleNow() {
Log.d(TAG, "Short lived task is done.")
}
/**
* Persist token to third-party servers.
*
* Modify this method to associate the user's FCM registration token with any server-side account
* maintained by your application.
*
* @param token The new token.
*/
private fun sendRegistrationToServer(token: String?) {
// TODO: Implement this method to send token to your app server.
Log.d(TAG, "sendRegistrationTokenToServer($token)")
}
/**
* Create and show a simple notification containing the received FCM message.
*
* @param messageBody FCM message body received.
*/
private fun sendNotification(messageBody: String) {
val intent = Intent(this, MainActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
val pendingIntent = PendingIntent.getActivity(this, 0 /* Request code */, intent,
PendingIntent.FLAG_IMMUTABLE)
val channelId = getString(R.string.default_notification_channel_id)
val defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
val notificationBuilder = NotificationCompat.Builder(this, channelId)
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setContentTitle(getString(R.string.fcm_message))
.setContentText(messageBody)
.setAutoCancel(true)
.setSound(defaultSoundUri)
.setContentIntent(pendingIntent)
.setPriority(NotificationCompat.PRIORITY_HIGH)
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
// Since android Oreo notification channel is needed.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(channelId,
"Channel human readable title",
NotificationManager.IMPORTANCE_HIGH)
notificationManager.createNotificationChannel(channel)
}
notificationManager.notify(R.string.default_notification_channel_id /* ID of notification */, notificationBuilder.build())
}
companion object {
private const val TAG = "MyFirebaseMsgService"
}
}
또한 작업시간이 길어질 경우 백그라운드에서 작업할 수 있도록 MyWorker 클래스도 생성해줍니다.
class MyWorker(appContext: Context, workerParams: WorkerParameters) : Worker(appContext, workerParams) {
override fun doWork(): ListenableWorker.Result {
Log.d(TAG, "Performing long running task in scheduled job")
// TODO(developer): add long running task here.
return ListenableWorker.Result.success()
}
companion object {
private val TAG = "MyWorker"
}
}
그리고 AndroidManifest.xml 파일의 application 태그내에 아래의 코드를 추가합니다.
<service
android:name=".MyFirebaseMessagingService"
android:exported="false"
android:directBootAware="true">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
app 수준 gradle 파일의 dependency는 아래처럼 구성했습니다.
implementation platform('com.google.firebase:firebase-bom:31.0.0')
implementation 'com.google.firebase:firebase-messaging-ktx'
implementation 'com.google.firebase:firebase-analytics'
implementation 'androidx.work:work-runtime:2.7.1'
implementation 'com.google.firebase:firebase-messaging-directboot'
MainActivity에서 노티피케이션 채널을 등록해줍니다.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// Create channel to show notifications.
val channelId = getString(R.string.default_notification_channel_id)
val channelName = getString(R.string.default_notification_channel_name)
val notificationManager = getSystemService(NotificationManager::class.java)
notificationManager?.createNotificationChannel(
NotificationChannel(channelId,
channelName, NotificationManager.IMPORTANCE_HIGH)
)
}
}
여기까지 설정 후 빌드 및 앱을 실행해줍니다.
궁금한점이나 잘못된 부분이 있다면 댓글로 알려주시면 감사하겠습니다!
reference
quickstart-android/messaging at master · firebase/quickstart-android
GitHub - firebase/quickstart-android: Firebase Quickstart Samples for Android
Firebase Quickstart Samples for Android. Contribute to firebase/quickstart-android development by creating an account on GitHub.
github.com