owned this note
owned this note
Published
Linked with GitHub
# Android、Kotlin 重大組件 ★★★
## 一、Koin
1. 定義的方法:
1. single -> 每次拿到同一個(單例) Ex:`val myModule = module{ single{ MyWood() as Wood} }`
2. factory -> 每次都New一個新的 Ex: `val myModule = module{ factory{ MyPrinter(get(),get()) as Printer} }`
3. scope -> 可以是一個範圍的人都取到同一個
2. 裡面會放get(),會自動拿取該class 照順序的資料型別,且這個get(),會再繼續尋找對應的建構式。
Ex:
```kotlin=
class Test(val code:Int, val student:Student)
val module = module{
factory{ Test(get(), get()) as Test}
//第一個get() = Int
//第二個get() = Student
}
```
3. 因為kotlin沒有隱式轉換,因此要用as,才會確定class;或是`single<HelloRepository> { HelloRepositoryImpl() }`。
4. 使用方式:
1. 創立一個di的資料夾,在裡面依照分類,先定義好modules
2. 自定義Application(繼承Application後,記得註冊檔要改成自定義的App),並於onCreate做koin的設置。
```kotlin=
startKoin{
androidContext(application-應用程式)//應用的範圍
androidLogger()//蒐集錯誤用,通常會用buildConfig Debug模式才開啟
modules(module)
}
```
3. modules,放入自己定義好的module,可以是一個moduleList
4. 在任何一個context base(Fragment也可以)的class,就可以透過by inject(lazy Loading) 或get(),拿到對應的資料。
5. 默認情況下,Koin將按類型或名稱綁定定義(如果該類型已綁定到定義),可以透過named()給他名字`single<Service>(named("Test")){ ServiceImpl2 ()}`,這樣如果有兩個相同的類型或名稱時,給予姓名,才會拿到不同的。[範例](https://insert-koin.io/docs/reference/koin-core/definitions)
6. 如果是要有參數的話,在調用時就使用parametersOf(args),將參數放入`val presenter : Presenter by inject { parametersOf(view) }
---
## 二、Room (Android Local DB)
### 概述
1. Room是把一些 SQLite 底層實作封裝起來讓我們能更方便存取資料庫,並支援編譯時期的SQL語法檢查。-也因此有些時候可能會打SQL的指令。
2. Room是操作資料庫的單一路口,等同於資料來源,包含了Room Database、Data Access Objects、Entities三個組成。
3. 內建可以跟LiveData或RxJava(Flowable)整合一起用,但都需要增加dependency。([範例](https://ansgarlin.github.io/android/room.html#More-about-DAO)
4. 搭配RxJava後,可以在資料改變時收到通知,但無法確定收到的資料是不是重複,因為Room不會知道是什麼造成變動,所以建議加上distinctUntilChanged()。
5. 執行過程:
1. 透過RoomDatabase取得DAO。
2. 呼叫DAO內的函式存入或取得Entity。
3. 針對Entity修改內容。
4. 示意圖:

6. MVVM Data Stream:


### Entity (修改內容):
1. 一定要設定這兩個宣告:
* 使用@Entity標記。
* 使用@PrimaryKey標記變數(作為主鍵)。
2. PraimaryKey (主鍵)注意事項:
* 變數的值是否唯一,不然會產生衝突。
* 如果預設值會是null,要加上@NonNull。(簡單來書,避免主鍵是null。)
3. 其他設定-非必要(設定在@Entity裡面):
* @tableName:設置表單名稱。(預設表單名稱是Entity類別名)
* @ColumnInfo:設置欄位名稱。(預設欄位名稱是變數名)
* @Index:用於標記特定欄位來加快查詢速度,如有特定組合是不希望重複的,可以再加上unique。
* @Ignore:用於不想被存入資料庫的變數。
* @ForeignKey:用於設定外部關聯。
* @Embedded:用於加入另一個Entity或POJO的變數。(跟其他的有關聯,可能會重名,會需要給名字)
* [使用範例](https://ansgarlin.github.io/android/room.html#Entity)
4. 可以有多個constructor(建構式),但參數名稱要和Entity內的變數名稱相同。
5. tableName 沒有大小寫之分(在SQLite裡)
6. 有設定ForeignKey,的話,可以使用`onDelete`或`onUpdate`設定外部資料變動時要做的事情;並且要注意存取有foreignKey的Entity十,資料庫要已經有指定的Entity,否則會錯誤。
7. 如果Embedded所擁有的變數名稱相同,可以透過加上prefix避免欄位重複發生錯誤。`@Embedded(prefix = "prefix_")`
8. ColumnInfo、Embedded、Relation(Pojo類才可適用)不能同時使用在同一個變數上。
### DAO (Data Access Objects)(函式存入或取得Entity)
1. 必要條件:
* 使用@Dao標記。
* 使用@Query、@Insert、@Update、@Delete、@Transaction標記函式。
2. Relation(用來創建POJO):
1. 利用`@Relation`設定變數。
2. 指定的變數,一定要是List或Set。
3. 並透過parentColumn指定變數所在類別對應到的欄位,而entityColumn則是變數類型內對應到的欄位。
EX:`@Relation(parentColumn = "uid", entityColumn = "id")`
4. 如果變數類型跟要聯合查詢的型別不同,可以另外用entity指定
EX: `@Relation(parentColumn = "uid", entityColumn = "id", entity = Address.class) `
5. 如果只是需要Entity的特定欄位,可以設定projection
EX: `@Relation(parentColumn = "uid", entityColumn = "id", entity = Address.class, projection = {"city"})`
3. @Query:取資料
1. 定義為:@Query("SELECT A FROM B")
* A = 要取的欄位,*代表全部,如果是多個,可以用逗號隔開
* B = 要找的Entity。
* FROM後面可以在加WHERE C(欄位) = D(參數) OR WHERE "欄位名稱" LIKE :參數名稱 表示有條件的尋找(篩選)
* LIMIT (Int) 是限制他 query 的數量
2. 如果沒有參數,代表回傳這個Entity的全部資料。
3. 如果參數是一個集合,只要再加上括號,Room就會自動將其內容分批執行然後回傳。(也就是用一個list去對照,拿取資料),回傳的list或個體自己決定。
4. 如果參數是變數(1或多個)可以將參數名稱加入指令內,並在前面加冒號,已進行篩選。(也就是輸入條件,拿取資料),回傳的list或個體自己決定。
5. 可以透過不同的資料表(table)來篩選要的結果回傳。
6. 如果需要多表查詢,可以建立一個POJO。
7. Primitive type的物件類型如Boolean、Long無法使用
8. [使用範例](https://ansgarlin.github.io/android/room.html#Query)
4. @Insert:放資料進資料庫
1. 可以放入單數個體、複數變數、陣列、集合。
2. 定義Insert時,可以設定好資料重複時的處理方式,否則PrimaryKey重複會錯誤。
3. 處理方式:`@Insert(onConflict = OnConflictStrategy.REPLACE)`
4. OnConflictStrategy.三種方式(有五種但兩種要被棄用):
* REPLACE:替換掉舊的資料。
* ABORT:終止並撤銷這次所做的任何更改。 - 閃退(是默認值)
* IGNORE:忽略掉這個資料。
* [詳細:ON CONFLICT條款](https://sqlite.org/lang_conflict.html)
5. insert 除了不回傳資料以外,也可以回傳rowid,如果是傳入多個或集合時,會回傳LongArray,一個的話就回傳Long。
5. @Update:用PrimaryKey搜尋資料,並更新資料內容(傳入一~多筆資料)。
6. @Delete:用PrimaryKey搜尋資料,並刪除資料(傳入一~多筆資料)。
7. Update跟Delete可以透過回傳int,取得成功更新/刪除的比數。
8. @Transaction:
1. 用來將DAO操作組合,確保全部操作在一個transaction內完成。
2. 如果跟@Query組合,則可以確保資料在兩種情況下不會出錯:
* 回傳結果超過CursorWindow(游標窗口?)一次能容納的量。
* 回傳結果是POJO類型,且其中的變數將是分開查詢。
9. 給予一個POJO,可以讓取資料的同時,將結果轉成較為精簡的資料類別。
### Database
1. 必要條件:
1. 必須是一個抽象類別,並繼承RoomDatabse。
2. 使用@Database標記,並給予會用到的Entity類別、資料庫版本。
3. 設定一個抽象並沒有接受參數的函式,取得Dao。
4. EX:`@Database(entities = {User.class}, version = 1)`
### TypeConverter
1. 如果有需要儲存Room無法支援的類別時,可以透過加上宣告@TypeConverters、@TypeConverter,來做轉換函式,不用自己一直打轉型。
2. 必要條件:
1. 要用@TypeConverter標記。
2. 轉換的函示的輸入參數須為想轉換的類別,轉出的類別則為Room可支援的類別。
3. 一個轉換的函示,要對應到一個還原的函示。
3. 使用方式:在Database的類別,告知額外可用的TypeConverter,EX:`@TypeConverters({Converters.class})`,之後在遇到對應的class時,就會自動轉換
### 如何使用
1. 取得Database:透過靜態函式databaseBuilder 並給予有@Database標記的類別和想要的資料庫名稱,實體的產生很耗資源建議singleton。
`Room.databaseBuilder(context: Context, dataClass: Class<T!>, name:String).build()`
2. 預設情況,存取資料庫(Insert、Query...)行為都強制在背景處理,否則會報錯(跟網路一樣)。
3. 如果需要在MainThread取得,必須在Builder,加上`allowMainThreadQueries()`。
4. 如果資料庫版本不同,可以進行整合,一樣是在Builder加上`addMigrations(Migration)`。
* Migration是一個內,放入兩個int,並override migrate方法即可。[範例](https://ansgarlin.github.io/android/room.html#Migration)
5. 如果有用Migration,要自行建立新的表單,才能將資料重新塞入,有兩種方式(一種透過SQL指令,一種透過內建的ContentValues)[範例](https://ansgarlin.github.io/android/room.html#Migration)
6. 如果確定不使用了,記得使用close()關閉資料庫,避免記憶體外洩。
7. builder還有很多可以設定的,有空可以研究。
### 如何測試(Unit Test)
1. @Before: 透過Room.inMemoryDatabaseBuilder()產生RoomDatabae,並呼叫getUserDao()取得DAO
2. @Test: 產生假資料,透過DAO加入資料庫,並透過DAO將資料取回並驗證。
3. @After: 執行close()關閉資料庫。
4. 測試Migration,要多做些前置作業(暫時用不到,之後有需要可以看範例後面的資料)。
5. [範例](https://ansgarlin.github.io/android/room.html#After)
參考資料:
[- ★Room (Android Interface Definition Language)](https://ansgarlin.github.io/zh-tw/android/room.html)
[- ★Room &MVVM](https://enginebai.com/2019/04/03/android-database-room/)
---
## 三、Rxjava (ReactiveX) - 合併未看
### 概述
1. 以資料為中心,關注點在資料的流動上。
2. Rxjava的缺點:快速的 Observable 和慢速的Observer 可能會迅速導致積壓的太多未消耗的資料,這將消耗系統資源,甚至可能導致OutOfMemoryException。 這個問題被稱為背壓 - (EX:WebSocket)。
3. 解決背壓,可以透過操作符,利用拿取的週期等方式,或是將Observable替換成Flowable的方式(Flowable其實跟Observable的創建方式相同)。
4. 既然使用Flowables不用擔心背壓,為什麼我不直接使用Flowables而要使用Observable呢? 答案是,Flowable比普通Observable需要更多的開銷,所以為了創造一個高性能的APP,你應該堅持使用Observable,除非你懷疑你的APP出現背壓。
5. reactive type共有五種:
* Flowable
* Observable
* Single
* Maybe
* Completable
### 使用方法
1. just():在裡面直接放入要讀取的資料,EX:`Observable.just(1,2,3,4,5)`。
2. from():能夠讀取繼承Iterable(遍歷器)的所有類別,EX:`Observable.from(listOf(1,2,3,4,5))`。
3. filter:過濾資料。
4. map:調整資料(一筆一筆處理),可以在這邊增加數值,甚至是轉換資料的格式也OK。
5. flatmap:直接將整筆資料轉換成另一個Observable。
6. sample(): 在指定時間間隔內檢查Observable的輸出,並拿取最後一個值。
7. throttleFirst():同上,但哪取第一個值
8. buffer():批量發射,存一段時間後,把這段時間的資料發射出去。
9. 如果只是需要觀察一個數值,可以利用Singles,非常輕量的Observable,只能接收一個值,當然後面可以利用方法來合併:
* mergeWith():將多個Singles合併成一個單一的Observable。
* concatWith():將多個Singles連在一起,形成Observable發射資料
* toObservable():將一個Single轉換為一個Observable,發出最初由Single發出的資料,然後完成。
### 關於Threading這回事
1. 發射資料的時間點,是在subscribe時,從哪個thread呼叫,預設thread就在哪裡,可以藉由subscribeOn、observeOn進行切換。
2. 會有三個階段(自己取的):
* 連線階段:一開始subscribe時,以當前的thread進行。
* 訂閱階段(Subscribe階段):拿取資料的過程,可能會有filter、map
* 完成階段:也就是Subscribe裡面的CallBack,這裡記得Observer的資料格式,如果上面有定義map,要跟map出來的資料格式相同。
* 資料拿到後會執行onNext()(依序將訊息處理完畢),當全部的onNext()都處理完後,就會觸發onComplete()
3. subscribeOn(影響訂閱階段Thread):
1. 改變subscribe執行的thread,通常只會被呼叫一次 。
2. 如果沒有定義subscribeOn,會沿用一開始訂閱階段的Thread。
4. observeOn(影響完成階段Thread):
1. 只會影響到在這個function以後的所有操作。
2. 不是全域的設定,可以被呼叫多次,呼叫的順序也會有影響。
3. 最後的呼叫會覆蓋掉之前的設定,會以最後一個observeOn為主(包括subscribeOn - 如果在observeOn之後才執行的話)
4. 如果沒有定義observeOn,會沿用subscribeOn的Thread。
5. 上述兩種都需要放入一個scheduler作為參數,預設有:
1. Schedulers.io():如果有網路、檔案存取需求推薦使用,Rx 會幫我們管理 ThreadPool reuse 的部分。
2. Schedulers.computation():計算數據推薦;跟 io() 一樣也是會由 ThreadPool 管理,但是 ThreadPool 的大小跟裝置 CPU core 相關。
3. Schedulers.newThread():顧名思義就是每次都會建立新的 Thread,所以稍微耗效能一點。
4. AndroidSchedulers.mainThread():Main Thread。 (需要額外引用library `implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'`)
6. subscribe執行時,會先跑上面原本的設定,除非是寫在subscribe之後的動作(或是subscribe的onNext)才會跑到observeOn上。
7. 範例:
```kotlin=
//假設有一串陣列1~6,要去讀取他,並在顯示時要轉換成String型態
Observable
//要讀的東西 - 可能是網路資料
.just(1,2,3,4,5,6)
//讀到後要不要過濾 - subscribe階段
.filter{
println("Filter " + Thread.currentThread().name)
it % 3 == 0}
//對資料有沒有更改 - subscribe階段 ->這裡可以改變資料的型態,下面的Observer要跟他相同
.map {
println("Map " + Thread.currentThread().name)
"${it * 3}" }
//定義subscribe階段要在哪個執行緒上
.subscribeOn(Schedulers.io())
//定義完成階段要在哪個執行緒上
.observeOn(Android.mainThread())
.subscribe(object : Observer<String>{
//訂閱頻道會看在哪個Thread呼叫,就是在哪裡,因此不受限subscribeOn的Thread
override fun onSubscribe(d: Disposable) {
println("onSubscribe " + Thread.currentThread().name)
}
//onNext、onComplete、onError都算完成階段,會被observerOn的Thread影響
override fun onNext(t: String) {
//資料已經處理好了,要做什麼,EX:顯示於畫面。
println("onNext / $t " + Thread.currentThread().name)
}
override fun onComplete() {
println("onComplete " + Thread.currentThread().name)
}
override fun onError(e: Throwable) {
println("onError " + Thread.currentThread().name)
}
})
```
參考資料:
[- [Android 十全大補] RxJava](https://ithelp.ithome.com.tw/articles/10223248)
[- 官方Reactive運算符](http://reactivex.io/documentation/operators.html)
[- Rxjava的操作符(sample,buffer...)](https://code.tutsplus.com/zh-hant/tutorials/reactive-programming-operators-in-rxjava-20--cms-28396)
[- Rxjava的創建符](https://code.tutsplus.com/zh-hant/tutorials/reactive-programming-operators-in-rxjava-20--cms-28396)
[- Rxjava的運用簡例](https://medium.com/jastzeonic/rxjava2-%E5%85%A5%E9%96%80%E9%82%A3%E4%B8%80%E5%85%A9%E5%80%8B%E5%BF%83%E5%BE%97-346d3affdf70)
[- 关于RxJava最友好的文章](https://www.jianshu.com/p/6fd8640046f1)
[- 给初学者的RxJava2.0教程(一)](https://www.jianshu.com/p/464fa025229e)
---
## 四、 MPAndroidChart
### [- MPAndroidChart LineChart使用,基本語法都在這裡可以查](https://bloggi.co/posts/1188fe)
### 基本使用
1. [ValueFormatter官方預設值](https://weeklycoding.com/mpandroidchart-documentation/formatting-data-values/)
2. 須按照生冪排序放資料(因為系統是用二分搜尋法)
3. Data:一張圖的資料(會分line、bar...)
* DataSet(一個資料可以包含很多的DataSet,每一個DataSet,代表一種資料的全部,以線,來說,一個DataSet,就是一條線)
* Entry (一個DataSet,可以有很多個Entry,每一個Entry,代表一個資料點會有x,y座標,也可以放資料)

---
## 五、SpanString && Im
1. 流程:
1. 取得要用到的文字(可以不是getString直接打)
2. 取得要開始改變的index
3. 先將第一組文字,new成 SpannableString
4. **如果開始改變的index不為-1;計算 結束的index(開始改變 + 要改變的程度)**
5. 設定Span(樣式,起始index,結束index,規則)
* 樣式、規則都有多個預設可選
2.
* 以下的前後指的是 startIndex (前), endInex (後)
* – Spannable.SPAN_EXCLUSIVE_EXCLUSIVE:前后都不包括,即在指定范围的前-面和后面插入新字符都不会应用新样式
* – Spannable.SPAN_EXCLUSIVE_INCLUSIVE :前面不包括,后面包括。即仅在范围字符的后面插入新字符时会应用新样式
* – Spannable.SPAN_INCLUSIVE_EXCLUSIVE :前面包括,后面不包括。
* – Spannable.SPAN_INCLUSIVE_INCLUSIVE :前后都包括。
```kotlin=
val browseTitle: String = getString(R.string.template_base_information_browse_title)
val smallerPartTitle: String = getString(R.string.template_base_information_browse_title_smaller)
val matchStart: Int = browseTitle.indexOf(smallerPartTitle)
val spannableString = SpannableString(browseTitle)
if (matchStart != -1) {
val matchEnd = matchStart + smallerPartTitle.length
/*
1.設定樣式 AbsoluteSizeSpan 字體大小改變的Span
前面包括,後面不包括
*/
spannableString.setSpan(
AbsoluteSizeSpan(14, true),
matchStart,
matchEnd,
Spannable.SPAN_INCLUSIVE_EXCLUSIVE
)
}
```
參考資料:
[- SpanString和SpanStringBuilder的使用](https://blog.csdn.net/u010126792/article/details/83538829)
[- Android SpannableString使用详解](https://blog.csdn.net/wenzhi20102321/article/details/54017417)
[- Android中的SpannableString與SpannableStringBuilder詳解](https://codertw.com/android-%E9%96%8B%E7%99%BC/335316/)
###### tags: `Android`