Kotlin နဲ့ Android ရေးကြမယ် (၂)
Part 1 ရေးပြီးတဲ့နောက် ဘယ်သူမှ စိတ်မဝင်စားလောက်ဘူးထင်တာနဲ့ ဆက်မရေးဖြစ်တာ ၁ နှစ်တောင်ကျော်သွားပြီ။ အခုတော့ Google ကိုယ်တိုင်က Kotlin ကို Android မှာ first-class language အနေနဲ့ သတ်မှတ်လိုက်ပြီ။ ဒါ့အပြင် စိတ်ဝင်စားတဲ့သူများလာတာလည်း ဖြစ်တဲ့အတွက် ဆက်ရေးဖို့ဆုံးဖြတ်လိုက်တာ ဖြစ်တယ်။
Extension Functions
Kotlin ရဲ့နောက်တော်တော်လေး အသုံးဝင်တဲ့တစ်ချက်က Extension Function တွေပဲဖြစ်တယ်။ Extension Function ဆိုတာ class တစ်ခုကို extend လုပ်စရာမလိုပဲရှိပြီးသား class ထဲမှာပဲ function တွေထပ်ထည့်လို့ရအောင် လုပ်ထားပေးတာဖြစ်ပါတယ်။ Java မှာဆိုရင် utility function ရေးရင်ရေး၊ မရေးချင်ရင် class ကို extend လုပ်ပြီး custom function တွေ ထပ်ထည့်ထားတာကိုသုံးမှပဲ ရပါတယ် ။ ဉပမာအနေနဲ့ပြမယ်ဆိုရင် ကျွန်တော်တို့ View တစ်ခုရဲ့ Visibility ကိုအများဆုံး ထားကြတာ View.VISIBLE သို့မဟုတ် View.GONE ပဲဖြစ်တယ်။ ဒါကြောင့်မလို့ setVisible Function တစ်ခုရေး, boolean တစ်ခုကို Argument ယူပြီး View.VISIBLE လား View.GONE လား သတ်မှတ်ပေးရင်မကောင်းဘူးလား။
Kotlin မှာ Project ထဲက ဘယ် kotlin file ကမဆို extension function တစ်ခုရေးထားရင် အဲဒီ extension function ကို ကျန်တဲ့ project နေရာတိုင်းကခေါ်ရေးလို့ရပါတယ်။ ဒါပေမဲ့ မြင်သာအောင်လို့ extension unction တွေကို ဆိုင်ရာဆိုင်ရာအောက်မှာ နာမည်သေချာပေးပြီးရေးတာ ပြန်ဖတ်ရလည်းပိုလွယ်ပါတယ်။
fun View.setVisible(isVisible: Boolean) { //body }
ဒီတစ်ကြောင်းက ဘာလုပ်တာလဲဆိုတော့ ကျွန်တော်တို့ View class ထဲမှာ setVisible ဆိုတဲ့ နာမည်နဲ့ function တစ်ခုထည့်ပေးမယ်လို့ compiler ကိုပြောလိုက်တာပါ။ ဒီမှာ ကိုယ် extend လုပ်ချင်တဲ့ Class Name ကို ထည့်ပေးရပါတယ်။ View. လို့ရေးထားတာကြောင့် View class file ထဲမှာ setVisible ဆိုတဲ့ function ဝင်သွားမယ်။ ဒါကြောင့်မလို့ လဲ function body မှာ this ဆိုပြီး ကိုယ်သုံးမဲ့ View Object တစ်ခုကို reference ယူလို့ရတာပါ။ ဒါကို ဘယ်လိုပြန်သုံးလဲဆိုရင်
val textView = findViewById(R.id.tv_hello_world) as TextView?textView?.setVisible(false);
စစက Android SDK ထဲက View class file တွေထဲမှာ setVisible ဆိုတဲ့ function မရှိပေမဲ့ ကျွန်တော်တို့ extension function ရေးလိုက်တာကြောင့် textView.setVisible ဆိုပြီး ခေါ်လို့ရသွားတာပဲဖြစ်ပါတယ်။ Kotlin မှာ အရာအားလုံးက Object တွေနဲ့ဖွဲ့စည်းထားလို့ နောက်တစ်နည်းပြောရမယ်ဆို ရှိသမျှ အရာတိုင်းအတွက် extension function ရေးလို့ရတယ်။ ဉပမာ အားဖြင့် Integer တစ်ခုရဲ့ နှစ်ထပ်ကိန်းကိုရှာချင်တယ်ဆိုပါဆို့။
fun Int.pow() : Int {
return this * this
}
ပြီးရင် တစ်ဖက်ကနေပြီး 3.pow(), 1.pow() အစရှိသဖြင့် အသုံးချလို့ရပါတယ်
println("3 power of 2 is " + 3.pow());
// 3 power of 2 is 9
Extension Function ကိုစိတ်ဝင်စားတယ်ဆိုရင် https://kotlinlang.org/docs/reference/extensions.html မှာဆက်ဖတ်ကြည့်လို့ရပါတယ်
Infix Function
ဒီထပ် ပိုကောင်းတဲ့ ထပ်ကိန်းရှာတဲ့ function တစ်ခုလောက် ထပ်ရေးကြည့်ရအောင်။
fun Int.powOf(a: Int): Int {
when (a) {
0 -> return 1;
else -> return this * powOf(a - 1);
}
}//Usage
println("2 power of 3 is " + 2.powOf(3))
ဒီ function က နောက်က ထပ်ကိန်းတန်ဖိုးပါထည့်ပြီး ရှာလို့ရသွားပြီ။ ဒါပေမဲ့ 2.powOf(3) ဆိုပြီးရေးရတာ ဖတ်ရတဲ့သူအတွက် ရှုပ်နေသလိုဖြစ်တယ်။ ဒီထက်ပိုပြီး ကြည့်ရရှင်းအောင် Kotlin ရဲ့ infix function ကိုသုံးကြည့်ရအောင်။ ခုနက ရှိပြီးသား function ရှေ့မှာ infix ဆိုတဲ့ keyword လေးထည့်လိုက်ရင်ရပါပြီ။
infix fun Int.powOf(a: Int): Int { //body }}
ဘာထူးခြားသွားလဲဆိုတော့ သူ့ကို ပြန်ခေါ်မယ်ဆိုရင် ခုနကလိုမျိုး 2 dot 3 အစရှိသဖြင့် ခေါ်မဲ့အစား dot(.) ခံစရာမလိုပဲ syntax တစ်ခုသဖွယ် နဲ့ အောက်ပါအတိုင်းသုံးလို့ရသွားမယ်။
println("2 power of 3 is " + (2 powOf 3))
ဒါဆိုရင် ဖတ်ရတဲ့သူအတွက်လည်း pseudocode ဆန်ဆန် ဖြစ်သွားတဲ့အထိ ရှင်းသွားတဲ့အတွက် Coding နားမလည်သူတွေအတွက်တောင် ကြည့်ရရှင်းနေမှာပါ။
တစ်ခုသတိ ထားရမှာက infix syntax သုံးမယ်ဆို extension functions တွေမှာ parameter တစ်ခုပဲရှိရမယ်။
Data Classes
Data Class ဆိုတာကတော့ ကျွန်တော်တို့ Java မှာကြုံဖူးကြတဲ့ POJO(Plain Old Java Object) တွေကို အလွယ်တကူ တစ်ကြောင်းတည်းနဲ့ ဖန်တီးလို့ရအောင် ပြုလုပ်ပေးထားတာပဲဖြစ်ပါတယ်။
data class Candidate (val username: String, val region: String, val legislature: String)
အသုံးပြုပုံကလည်း ရိုးရှင်းတယ်။ အထက်မှာပြထားတာဆိုရင် ကျွန်တော်တို့ username(String), region(String) နဲ့ legislature(String) ပါဝင်တဲ့ Candidate Object တစ်ခုရဲ့ POJO ပဲဖြစ်တယ်။ ဒီလိုရေးလိုက်တာနဲ့ equals, hashCode, toString နဲ့ componentN function တွေကို compiler ကအလိုအလျှောက်ထုတ်ပေးပါတယ်။ toString function ကလည်း ဖတ်လို့လွယ်တဲ့ format နဲ့ထုတ်ပေးတာဖြစ်လို့ ကိုယ်တိုင် ဘာမှ လုပ်စရာမလိုဘူး။
val candidate = Candidate("Foo", "Bar", "lower_house")
println(candidate.username)
println(candidate.toString())
//Output:
//Foo
//Candidate(username=Foo, region=Bar, legislature=lower_house)
Java မှာဆို ဒီအတွက် ကိုပဲအများကြီးရေးရပါတယ်။
Data Class ကိုစိတ်ဝင်စားရင် https://kotlinlang.org/docs/reference/data-classes.html မှာဆက်လေ့လာလို့ရပါတယ်။
componentN Function
ကျွန်တော်တို့ Java မှာဆိုရင် Object တစ်ခုမှာရှိတဲ့ variable နှစ်ခုကို ယူမယ်ဆိုရင် အောက်ပါအတိုင်း အရှည်ကြီးရေးရတယ်။
JavaCandidate candidate = new JavaCandidate("Foo", "Bar", "lower_house");
String name = candidate.name_english, legislature = candidate.legislature;
System.out.println(name + " is competing for " + legislature);
Kotlin မှာ အောက်ပါအတိုင်း အလွယ်တကူရေးလို့ရတယ်။
val candidate = Candidate("Foo", "Bar", "lower_house")
val (username, region, legislature) = candidate
println("$username is competing for $legislature")
ဒါဆိုရင် သူက Candidate data class မှာ ပထမဆုံး ကိုယ်ပေးခဲ့တဲ့ variable က username ထဲရောက်သွားမယ်။ နောက်တစ်ခုက region ထဲရောက်သွားမယ်၊ တတိယမြောက်က legislature ထဲရောက်သွားမယ်။ ပြောချင်တာက ကိုယ်ပေးခဲ့တဲ့ order အတိုင်းဆွဲထုတ်သွားပေးလိမ့်မယ်။ ဒီမှာ ကျွန်တော်တို့က region ကိုဆက်ပြီး အသုံးမချတဲ့အတွက်ကြောင့် underscore ( _ ) နဲ့ ပြောင်းရေးပေးလိုက်လို့ရပါတယ်။
val (username, _, legislature) = candidate
ဒါဆိုရင်တော့ program က region အတွက် သူက memory allocate လုပ်မှာမဟုတ်တော့ပါဘူး။
componentN Function ကိုစိတ်ဝင်စားတယ်ဆိုရင် https://kotlinlang.org/docs/reference/multi-declarations.html မှာဆက်လေ့လာလို့ရပါတယ်။
Higher Order Function
function တစ်ခုရဲ့ parameter မှာ နောက် function တစ်ခုထည့်ပေးရင်သော်လည်းကောင်း၊ function ကနေပြီး function return ပြန်ရင် အဲဒီ function ကို Higher Order Function လို့ခေါ်တယ်။
ဒီမှာကြည့်မယ်ဆိုရင် ကိုယ်စားလှယ်လောင်းတစ်ယောက်မှာ သူ့နာမည်နဲ့သော်လည်းကောင်း၊ သူပြိင်မဲ့ လွှတ်တော်နဲ့သော်လည်းကောင်း၊ ကြိုက်တာနဲ့ filter လုပ်လို့ရတယ်ဆိုပါဆို့။ ဒီအတွက် function တစ်ခုချင်းစီ လိုက်ရေးရင် အချိန်ကြာတယ်။အကုန်လုံးက ဝိုင်းသုံးလို့ရတဲ့ Higher Order function တစ်ခုကိုရေးထားမယ်ဆိုရင် ဒီ higher order function က ဘယ်နည်းနဲ့ filter လုပ်လဲသိစရာမလိုဘူး။ သူ့တာဝန်က ပေးထားတဲ့ function ကို အသုံးချပြီး filter လုပ်ဖို့ပဲဖြစ်သွားမယ်။ ဆိုလိုတာက Codebase မှာတစ်ခုနဲ့တစ်ခု ငြိနေတာတွေနည်းသွားတဲ့အတွက်ပြင်ဖို့ လည်းပိုလွယ်သွားမယ်။
fun filterCandidates(candidates: Collection<Candidate>, predicate: (Candidate) -> (Boolean)): List<Candidate> { //method body }}
filterCandidates
ဆိုတဲ့ function က parameter နှစ်ခုယူပြီး Candidate List တစ်ခု return ပြန်ပေးတယ်။ ပထမ parameter က Candidate Collections တစ်ခုကိုလက်ခံတယ်။ ဒုတိယ parameter က Candidate Object တစ်ခုကိုလက်ခံပြီး Boolean Return ပြန်ပေးမဲ့ function တစ်ခုကို လက်ခံတယ်။ predicate ဆိုတာ လှမ်းခေါ်လို့ရအောင် သုံးတဲ့ identifier ဖြစ်လို့ ကိုယ်ကြိုက်သလို နာမည်ပေးလို့ရတယ်။
for (candidate in candidates) {
if (predicate.invoke(candidate)) candidatesList.add(candidate)
}
Higher order function ထဲကိုဝင်လာတဲ့ function ကိုသုံးမယ်ဆိုရင်တော့ {fuction_identifier}.invoke() ဆိုတဲ့ keyword နဲ့သုံးလို့ရတယ်။ ဒီ higher oder function ကို တစ်ဖက်ကနေပြီးလှမ်းသုံးမယ်ဆိုရင် အောက်ပါအတိုင်းအသုံးပြုလို့ရတယ်။
val filteredList = filterCandidates(getCandidates(), {candidate -> candidate.legislature == "lower_house" }))
ကျွန်တော်တို့ Part 1 မှာ lambda အသုံးပြုသလိုမျိုးပဲ Anonymous function တစ်ခုထည့်ပေးလိုက်ရင် ကိုယ်လိုသလို filter လုပ်လို့ရသွားမှာပါ။ Anonymous function တစ်ခုမထည့်ပေးချင်ရင်လည်း ရေးထားပြီးသား function တစ်ခုရဲ့ identifier ကိုပို့ပေးလိုက်လည်းရပါတယ်။ Anonymous function မှာမှ ဒီထက်ပိုတိုချင်တယ်ဆိုရင် ဒီလိုရေးလို့လည်းရပါတယ်။
val filteredList = filterCandidates(getCandidates(), {candidate -> candidate.legislature == "lower_house" }))
Kotlin မှာ Anonymous function က parameter တစ်ခုတည်းဆိုရင် ‘it’ ဆိုတဲ့ keyword နဲ့ လှမ်းသုံးလို့ရပါတယ်။
Higher Oder Function အကြောင်းဆက်လက်လေ့လာချင်တယ်ဆိုရင် https://kotlinlang.org/docs/reference/lambdas.html မှာလေ့လာလို့ရပါတယ်။