Android 連接FCM(Firebase Cloud Messaging)問題?

出現以下關鍵錯誤訊息
Could not parse the Android Application Module’s Gradle config. Resolve gradle build issues and/or resync.

解決方法

發生此問題有兩個原因

  1. Project的build.gradle將jcenter()刪除,如果沒解決,在看2
  2. 如果1個Project有好幾個Model,先將Model設為id ‘com.android.application’
    每個Model各別Connect to Firebase,直到顯示Connected為止
    之後再改回id ‘com.android.library’

訂閱Codeilin的旅程,若有最新消息會通知。

廣告

Android 連結FCM(Firebase Cloud Messaging)

1. 摘要
2. 新增FCM流程
3. 寫一個FCM Service
4. 在AndroidManifest.xml內新增
5. Activity中獲取token

1. 摘要

將Android Studio與Firebase Cloud Messaging連結的簡易流程

2. 新增FCM流程

  1. 如果有Model必須將每個Model各別Connect to Firebase,直到顯示Connected
  2. 如果有Model必須將每個Model各別Add FCM to your app,直到顯示Dependencies set up correctly

3. 寫一個FCM Service

class FCMServer: FirebaseMessagingService() {
    companion object {
        var fcmToken = ""
    }

    override fun onNewToken(token: String) {
        super.onNewToken(token)
        fcmToken = token
        println("NEW_TOKEN $token")
    }

    override fun onMessageReceived(remoteMessage: RemoteMessage) {
        if(remoteMessage.data.isNotEmpty()) {
        }
    }

    override fun onMessageSent(p0: String) {
        super.onMessageSent(p0)
    }
}

4. 在AndroidManifest.xml內新增

<service
    android:name=".FCMServer"
    android:exported="false">
    <intent-filter>
        <action android:name="com.google.firebase.MESSAGING_EVENT" />
    </intent-filter>
</service>

5. Activity中獲取token

private fun initFCM() {
FirebaseMessaging.getInstance().token.addOnCompleteListener(OnCompleteListener { task ->
if (!task.isSuccessful) {
//Log.w(TAG, "Fetching FCM registration token failed", task.exception)
println("Fetching FCM registration token failed ${task.exception}")
return@OnCompleteListener
}
println("fcmToken ${task.result}")
// Get new FCM registration token
FCMServer.fcmToken = task.result
})
}

訂閱Codeilin的旅程,若有最新消息會通知。

廣告

Android 更新Gradle版本後,出現Java版本問題?

出現以下關鍵錯誤訊息
Android Gradle plugin requires Java 11 to run. You are currently using Java 1.8.

在Gradle版本AGP 7.0.0-alpha02後,要求使用Java 11

解決方法


訂閱Codeilin的旅程,若有最新消息會通知。

廣告

Android Studio運行main編譯錯誤問題?

問題

當執行public static void main(String[] args)時,報出以下錯誤訊息

SourceSet with name ‘main’ not found.

解決方法

如下,新增下面那行即可編譯

<option name="delegatedBuild" value="false" />


訂閱Codeilin的旅程,若有最新消息會通知。

廣告

Android JSON快速檢閱與快速轉換Class方法

快速整理JSON

網站:JSON Formatter網站

輸入後按Process
下方會出現整理後的內容

快速將JSON生成Kotlin Class

下載後按OK

再來新增一個空白的Kotlin Class,在空白Kotlin Class介面

Windows按下 Alt+K

Mac按下 option+K

輸入JSON、Class Name後,按下Generate
如果JSON格式正確,就會自動生成data class囉

訂閱Codeilin的旅程,若有最新消息會通知。

廣告

Android 根據螢幕自動縮放的Size單位

摘要

一般的dp單位並不會隨螢幕尺寸做縮放,這裡介紹一個Library,可以根據螢幕尺寸做縮方

一個新的單位sdp,Github

使用時需要注意,有時候可能會根據平板做不同的Layout設計

使用方法

新增依賴

在app build.gradle新增以下依賴

dependencies {
    ...
    implementation 'com.intuit.sdp:sdp-android:1.0.6'
}

使用

以@dimen/_5sdp格式使用,如下

android:bottom="@dimen/_5sdp"

訂閱Codeilin的旅程,若有最新消息會通知。

廣告

Android Navigation搭配BottomNavigationView

1. 摘要
2. 使用方法
3. BottomNavigationView取消內建顏色變化

1. 摘要

參考:Navigation使用官網Android Jetpack Navigation與Safe Args使用

Library引用方法,以及基本的設定,請參考Android Jetpack Navigation與Safe Args使用

這裡主要是介紹「Navigation搭配BottomNavigationView使用方法」

2. 使用方法

在Layout中新增

<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintTop_toBottomOf="@+id/toolbar"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">

<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/nav_view"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="0dp"
android:layout_marginEnd="0dp"
android:background="?attr/colorPrimaryVariant"
app:labelVisibilityMode="unlabeled"
app:itemIconSize="@dimen/icon_45"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:menu="@menu/bottom_nav_menu" />

<fragment
android:id="@+id/nav_host_fragment_activity_main"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="@id/nav_view"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/local_navigation" />
</androidx.constraintlayout.widget.ConstraintLayout>

onCreate新增

val navController = findNavController(activity, R.id.nav_host_fragment_activity_main)

定義AppBarConfiguration

以下的R.id是BottomNavigationView的menu,menu/bottom_nav_menu裡面的

val appBarConfiguration = AppBarConfiguration(setOf(
    R.id.navigation_home,
    R.id.navigation_manager,
    R.id.navigation_account,
    R.id.navigation_data
))

設定appBarConfiguration

setupActionBarWithNavController(navController, appBarConfiguration)
setupWithNavController(navController)

點擊BottomNavigationView監聽方式

navController.addOnDestinationChangedListener { _, destination, _ ->
    when (destination.id) {
        R.id.navigation_home -> {
            
        }
        R.id.navigation_manager -> {
            
        }
        R.id.navigation_account -> {
            
        }
        R.id.navigation_data -> {
            
        }
    }
}

3. BottomNavigationView取消內建顏色變化

bottomNavigationView.itemIconTintList = null

這個方法目前無法於xml中設定

設定後,BottomNavigationView Menu就可以使用比較複雜的icon


相關文章

Android Jetpack Navigation與Safe Args使用
1. 摘要
2. 新增Navigation依賴
3. 基本使用方法
4. 使用Navigation Safe Args

訂閱Codeilin的旅程,若有最新消息會通知。

廣告

Android Jetpack Navigation與Safe Args使用

1. 摘要
2. 新增Navigation依賴
3. 基本使用方法
4. 使用Navigation Safe Args

1. 摘要

參考:Navigation使用官網Safe Args傳遞資料官網

使用Navigation 做Fragment之間的切換,可以更便利,更清楚誰與誰之間做導航

Safe Args 傳遞安全的數據,是官網強烈建議使用,當然也可以不使用,用原本的Bundle做傳遞

2. 新增Navigation依賴

在build.gradle中新增以下

dependencies {
    ...
    implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5'
    implementation 'androidx.navigation:navigation-ui-ktx:2.3.5'
}

3. 基本使用方法

在app/src/main/res新增Direction: navigation

在navigation資料夾上新增

navigation裡面新增以下程式碼

<navigation 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:id="@+id/navigation"
app:startDestination="@id/navHomeFragment">
<fragment
android:id="@+id/navHomeFragment"
android:name="com.example.myapplication.fragment.NavHomeFragment"
android:label="NavHomeFragment"
tools:layout="@layout/fragment_nav_home">
<action
android:id="@+id/action_navHomeFragment_to_navSecondFragment"
app:destination="@id/navSecondFragment" />
</fragment>
<fragment
android:id="@+id/navSecondFragment"
android:name="com.example.myapplication.fragment.NavSecondFragment"
android:label="NavSecondFragment"
tools:layout="@layout/fragment_nav_second"/>
</navigation>

startDestination是起始的fragment
action是拉線後自動會新增的,看id名字很明顯可以知道是哪導航到哪

在Activity的layout中新增以下程式碼

<fragment
android:id="@+id/fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:defaultNavHost="true"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:navGraph="@navigation/navigation"/>

navGraph是剛剛新增的navigation.xml
androidx.navigation.fragment.NavHostFragment是固定的別打錯
app:defaultNavHost="true" 屬性確保您的 NavHostFragment 會攔截系統返回按鈕

Activity onCreate新增Nav

lateinit var navController: NavController
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_navigation)
    navController = Navigation.findNavController(this, R.id.fragment)
    NavigationUI.setupActionBarWithNavController(this, navController)
}

override fun onSupportNavigateUp(): Boolean {
    //按back返回的方法
    return navController.navigateUp()
}

Fragment 更換介面方式

下面的id是先前那個action的id

可以帶入bundle,也可以不帶入

val bundle = bundleOf(
"name" to firstName,
"mobile" to mobile.toLong()
)

findNavController().navigate(
R.id.action_enterDetailsFragment_to_verifyDetailsFragment,
bundle
)

讀取bundle方式,在目的地的Fragment onCreateView,新增以下程式碼

val name = arguments?.getString("name")
val mobileNumber = arguments?.getLong("mobile")

4. 使用Navigation Safe Args

官方強烈建議您將 Safe Args 用於導航和數據傳遞,因為它可以確保類型安全

新增依賴

在Project build.gradle新增以下

dependencies {
    def nav_version = "2.3.5"
    classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
}

在app build.gradle新增以下

plugins {
    id 'androidx.navigation.safeargs.kotlin'
}

新增完依賴後

Build -> clean Project

Build -> Rebuild Project

選擇Android
在Java(generated)會出現這張圖的內容

Android Studio 4.1以上版本,如果navigation已建立好,並新增上述依賴,如果沒出現的話

在app build.gradle內新增以下內容

android {
    sourceSets {
        getByName("main").java.srcDirs("build/generated/source/navigation-args")
    }
}

新增好後

Build -> clean Project

Build -> Rebuild Project

此時會在Java內出現這張圖的內容

如果有出現就是成功添加囉

使用方法

原本是以下程式碼

val bundle = bundleOf(
    "name" to firstName,
    "mobile" to mobile.toLong()
)
findNavController().navigate(
    R.id.action_enterDetailsFragment_to_verifyDetailsFragment,
    bundle
)

使用safe args時,至navigation VerifyDetailsFragment 新增argument

<fragment xxxxx>
    <argument android:name="name"
        app:argType="string"/>
    <argument android:name="mobile"
        app:argType="long"/>
</fragment>

新增好後Build -> Make Project

就可以改用下方用法囉

findNavController().navigate(
EnterDetailsFragmentDirections.actionEnterDetailsFragmentToVerifyDetailsFragment(
        firstName,
        mobile.toLong()
    )
)

讀取方法如下

原本讀取方法

val name = arguments?.getString("name")
val mobileNumber = arguments?.getLong("mobile")

變更為以下方法

val args: VerifyDetailsFragmentArgs by navArgs()
args.let {
println("name ${it.name}, mobile ${it.mobile}")
}

這就是使用Safe Args的方法


相關文章

Android Navigation搭配BottomNavigationView
1. 摘要
2. 使用方法
3. BottomNavigationView取消內建顏色變化

訂閱Codeilin的旅程,若有最新消息會通知。

廣告

Android Studio 使用Gradle生成jar與aar

jar與aar差異

jar僅能含java class不能含有資源

aar除了java class外,還可以包括資源在內

一個library如果要編譯成aar,不能implementation aar,否則會編譯失敗,但可以含有jar

生成jar

  • 打開專案Gradle
  • 在Gradle內將
//id 'com.android.application'註解
id 'com.android.library'改為這個
//applicationId 'com.xxx.xxx'這個也註解
  • 在Gradle最下方新增下方程式碼
task deleteJar(type: Delete) {
    delete 'libs/commonTest.jar'
}


task createJar(type: Copy) {
    from('build/intermediates/compile_library_classes_jar/release/')
    into('libs/')
    include('classes.jar')
    rename('classes.jar', 'commonTest.jar')
}

createJar.dependsOn(deleteJar, build)
之後點選Sync Now
  • 再來打開右方Gradle,點選createJar
雙擊兩下執行

備註:這裡需要使用Tasks,如果找不到Tasks
參考這個文章Android Studio 右側Gradle Tasks選項消失?

  • 執行完後會在app/libs下方生成
這樣就成功編譯出Jar囉

備註:刪除jar一樣,找到deleteJar雙擊兩下執行,就可以囉

生成aar

  • 打開專案Gradle
  • 在Gradle內將
//id 'com.android.application'註解
id 'com.android.library'改為這個
//applicationId 'com.xxx.xxx'這個也註解
點選兩個其中一個
會在此生成aar


訂閱Codeilin的旅程,若有最新消息會通知。

廣告

Java Collections HashMap、LinkedHashMap、HashSet

1. HashMap
2. LinkedHashMap
3. HashSet
4. HashSet 與 ArrayList執行效率比較

HashMap

  1. 可以有N個Key與Value,但一個map只會有一個key存在
  2. 型態可以根據初始化來決定
  3. 不會按順序排序
Map<String, Integer> map1 = new HashMap<>();
map1.put("k123", 0);
map1.put("k123", 0);
map1.put("f345", 1);
map1.put("a234", 2);
map1.put("b234", 4);
map1.put("c234", 3);
System.out.println();
for(String map: map1.keySet()) {
    System.out.print(map + ", ");
}
//資料沒按照put的順序排列
//打印f345, k123, b234, c234, a234, 
//Location為class
private Map<Integer, Location> locations = new HashMap<>();
public static void main(String[] args) {
    Map<String, Integer> tempExit = new HashMap<>();
    //新增map,
    locations.put(0, new Location(0,"data1", tempExit));
    //避免曝露,如下刪除後,如果曝露,變更參數後,則會影響內部資料
    tempExit.remove("S");
}

//Location 
//this.map = map; 如果這樣的話,外部數值變化,內部也會跟著變化
//避免曝露,內部需要改成這樣,外部數值變化,內部並不會改變
if(map != null) {
    this.map = new HashMap<>(map);
} else {
    this.map = new HashMap<>();
}

可以使用以下方式找到對應的key,以及讀取

//map -> Map<String, Integer> map;
if(map.containsKey("key1")) {
    //找到此key
    int data = map.get("key1")
}

遍歷Map

for(String map: mpas.keySet()) {
    System.out.print(map + ", ");
}

for(Map.Entry<String, Integer> map: exits.entrySet()) {
    System.out.println("key: " + map.getKey() + 
                        ", value: " + map.getValue());
}

Collections.unmodifiableMap禁止修改Map

可以利用return,限制是否禁止修改Map

public Map<String, Integer> getMap() {
    return Collections.unmodifiableMap(exits);
}

LinkedHashMap

Map<String, Integer> map1 = new LinkedHashMap<>();
map1.put("k123", 0);
map1.put("k123", 0);
map1.put("f345", 1);
map1.put("a234", 2);
map1.put("b234", 4);
map1.put("c234", 3);
System.out.println();
for(String map: map1.keySet()) {
    System.out.print(map + ", ");
}
//資料按照put的順序排列
//打印k123, f345, a234, b234, c234, 

其餘用法與HashMap相同

HashSet

使用方法與List相似,但只會有一個key存在,但主要判斷是否為相同是根據hashCode

但hashCode,主要是equals return true的時候,才會觸發

因此重寫hashCode就必須要重寫equals,反過來也是

如果把下方程式碼TestClass裡面的hashCode、equals兩個註解

會打印

Data2
Data1
Data2
Data1

如果沒註解,則打印

Data1
Data2

private static Set<TestClass> testMap = new HashSet<>();

public static void main(String[] args) {
TestClass temp = new TestClass("Data1");
testMap.add(temp);
temp = new TestClass("Data1");
testMap.add(temp);
temp = new TestClass("Data2");
testMap.add(temp);
temp = new TestClass("Data2");
testMap.add(temp);
for(TestClass testClass: testMap) {
System.out.println("\t" + testClass.getKey());
}
}
public class TestClass {
    private Key key;

    public TestClass(String name) {
        this.key = new Key(name);
    }

    //將hashCode、equals兩個註解,會有不一樣的結果
    @Override
    public int hashCode() {
        return this.key.hashCode();
    }

    @Override
    public boolean equals(Object obj) {
        if(this == obj) {
            return true;
        }
        if(obj instanceof TestClass) {
            TestClass theObject = (TestClass) obj;
            return this.key.equals(theObject.key);
        }
        return false;
    }

    public Key getKey() {
        return key;
    }

    public static class Key {
        private String name;

        public Key(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }

        @Override
        public int hashCode() {
            return this.name.hashCode() + 57;
        }

        @Override
        public boolean equals(Object obj) {
            if(obj instanceof Key) {
                Key key = (Key) obj;
                if (this.name.equals(key.getName())) {
                    return true;
                }
            }
            return false;
        }

        @Override
        public String toString() {
            return this.name;
        }
    }
}

HashSet 與 List執行效率比較

當10000筆資料做contains時

耗時:HashSet(1ms) < ArrayList(229ms)

//    private static Set<TestClass> testMap = new HashSet<>();
private static List<TestClass> testMap = new ArrayList<>();
public static void main(String[] args) {
long startTime;
long endTime;
long executeTime;
TestClass temp;
for(int i=0;i<10000;i++) {
temp = new TestClass("Data" + i);
testMap.add(temp);
}

startTime = System.currentTimeMillis();
for(TestClass testClass: testMap) {
if(testMap.contains(testClass)) {
}
}
endTime = System.currentTimeMillis();
executeTime = endTime - startTime;
System.out.println("executeTime: " + executeTime + "ms");
//HashSet 1ms
//ArrayList 229ms
}

當100000筆,新增資料時

耗時:HashSet(58ms) > ArrayList(42ms)

    private static Set<TestClass> testMap = new HashSet<>();
//    private static List<TestClass> testMap = new ArrayList<>();
    public static void main(String[] args) {
        long startTime;
        long endTime;
        long executeTime;
        TestClass temp;

        startTime = System.currentTimeMillis();
        for(int i=0;i<10000;i++) {
            temp = new TestClass("Data" + i);
            testMap.add(temp);
        }
        endTime = System.currentTimeMillis();
        executeTime = endTime - startTime;
        System.out.println("executeTime: " + executeTime + "ms");
        //HashSet 25ms
        //ArrayList 23ms
    }

當100000筆,刪除資料時

耗時:HashSet(18ms) < ArrayList(490ms)

//    private static Set<TestClass> testMap = new HashSet<>();
    private static List<TestClass> testMap = new ArrayList<>();
    public static void main(String[] args) {
        long startTime;
        long endTime;
        long executeTime;
        TestClass temp;

        for(int i=0;i<10000;i++) {
            temp = new TestClass("Data" + i);
            testMap.add(temp);
        }
        startTime = System.currentTimeMillis();
        for(int i=0;i<10000;i++) {
            temp = new TestClass("Data" + i);
            testMap.remove(temp);
        }
        executeTime = endTime - startTime;
        System.out.println("executeTime: " + executeTime + "ms");
        //HashSet 18ms
        //ArrayList 490ms
    }

當100000筆,removeAll時

耗時:HashSet(69ms) < ArrayList(27252ms)

    private static Set<TestClass> testMap = new HashSet<>();
// private static List<TestClass> testMap = new ArrayList<>();
public static void main(String[] args) {
long startTime;
long endTime;
long executeTime;
TestClass temp;

startTime = System.currentTimeMillis();
for(int i=0;i<100000;i++) {
temp = new TestClass("Data" + i);
testMap.add(temp);
}
testMap.removeAll(testMap);
endTime = System.currentTimeMillis();
// startTime = System.currentTimeMillis();
// for(TestClass testClass: testMap) {
// if(testMap.contains(testClass)) {
// }
// }
// endTime = System.currentTimeMillis();
executeTime = endTime - startTime;
System.out.println("executeTime: " + executeTime + "ms");
//HashSet 69ms
//ArrayList 27252ms
}

整體看起來HashSet效率優於ArrayList


相關文章

訂閱Codeilin的旅程,若有最新消息會通知。

廣告

透過 WordPress.com 建置的網站.

向上 ↑