2018年4月21日土曜日

kotlin.math を使う

PI などの定数や sin などの計算をするとき Java の Math クラスを使うことが多いと思いますが、Kotlin 1.2 から数学関数と定数を提供する kotlin.math パッケージが追加されています。

この中には E や PI などの定数、三角関数、双曲線関数、べき乗、対数、丸め、符号と絶対値が含まれています。

試しに全部使ってみました。基本 Float を使ってますが Double も用意されています。 package net.yanzm.sample import kotlin.math.* fun main(args: Array) { println(E) // 2.718281828459045 println(PI) // 3.141592653589793 // unit in the last place println(PI.ulp) // 4.440892098500626E-16 // the remainder of division println(10f.IEEErem(3f)) // 1.0 // // sign and absolute value // println(10.sign) // 1 println((-10).sign) // -1 println(sign(10f)) // 1.0 println(sign(-10f)) // -1.0 println((1.1f).withSign(-2f)) // -1.1 println((1.1f).withSign(2f)) // 1.1 println((-1.1f).withSign(-2f)) // -1.1 println((-1.1f).withSign(2f)) // 1.1 println(10.absoluteValue) // 10 println((-10).absoluteValue) // 10 println(abs(10)) // 10 println(abs(-10)) // 10 // // rounding // println(ceil(1.1f)) // 2.0 println(ceil(1.5f)) // 2.0 println(ceil(-1.1f)) // -1.0 println(ceil(-1.5f)) // -1.0 println(floor(1.1f)) // 1.0 println(floor(1.5f)) // 1.0 println(floor(-1.1f)) // -2.0 println(floor(-1.5f)) // -2.0 println(truncate(1.1f)) // 1.0 println(truncate(1.5f)) // 1.0 println(truncate(-1.1f)) // -1.0 println(truncate(-1.5f)) // -1.0 println(round(1.1f)) // 1.0 println(round(1.5f)) // 2.0 println(round(-1.1f)) // -1.0 println(round(-1.5f)) // -2.0 println((1.1f).roundToInt()) // 1 println((1.5f).roundToInt()) // 2 println((-1.1f).roundToInt()) // -1 println((-1.5f).roundToInt()) // -2 println((1.1f).roundToLong()) // 1L println((1.5f).roundToLong()) // 2L println((-1.1f).roundToLong()) // -1L println((-1.5f).roundToLong()) // -2L // // trigonometric // println(sin(PI / 2)) // 1.0 println(asin(1f)) // 1.5707964 println(cos(PI)) // -1.0 println(acos(-1f)) // 3.1415927 println(tan(PI / 4)) // 0.9999999999999999 println(atan(1f)) // 0.7853982 println(atan2(5f, 5f)) // 0.7853982 // // hyperbolic // println(sinh(0.8813736f)) // 1.0 println(asinh(1f)) // 0.8813736 println(cosh(0f)) // 1.0 println(acosh(1f)) // 0.0 println(tanh(0.54930615f)) // 0.5 println(atanh(0.5f)) // 0.54930615 // // exponentiation and power // println(exp(0f)) // 1.0 println(exp(1f)) // 2.7182817 println(expm1(0f)) // 0.0 println(expm1(1f)) // 1.7182817 println(2f.pow(2)) // 4.0 println(sqrt(4f)) // 2.0 // sqrt(x^2 + y^2) println(hypot(3f, 4f)) // 5.0 // // logarithmic // println(ln(E)) // 1.0 println(ln(1f)) // 0.0 // ln(x + 1) println(ln1p(E - 1)) // 1.0 println(ln1p(0f)) // 0.0 println(log(10f, 10f)) // 1.0 println(log(1f, 10f)) // 0.0 println(log(2f, 2f)) // 1.0 println(log(1f, 2f)) // 0.0 // log(x, 10) println(log10(10f)) // 1.0 println(log10(1f)) // 0.0 // log(x, 2) println(log2(2f)) // 1.0 println(log2(1f)) // 0.0 // // max, min // println(max(1, 2)) // 2 println(max(1f, 2f)) // 2.0 println(min(1, 2)) // 1 println(min(1f, 2f)) // 1.0 // // nearest // println((1.1f).nextDown()) // 1.0999999 println((-1.1f).nextDown()) // -1.1000001 println((1.1f).nextUp()) // 1.1000001 println((-1.1f).nextUp()) // -1.0999999 println((1.1f).nextTowards(0f)) // 1.0999999 println((-1.1f).nextTowards(0f)) // -1.0999999 }


2018年4月12日木曜日

ViewPager + Fragment で AAC の ViewModel を使う

各ページが Fragment な ViewPager があるとします。 class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val pager: ViewPager = findViewById(R.id.pager) pager.adapter = MyPagerAdapter(supportFragmentManager) } class MyPagerAdapter(fm: FragmentManager) : FragmentStatePagerAdapter(fm) { override fun getItem(position: Int): Fragment { return PageFragment.newInstance(position) } override fun getCount(): Int { return 10 } } } FragmentStatePagerAdapter を使った場合、不要になったページの Fragment インスタンスは破棄されます。
(FragmentPagerAdapter では一度作った Fragment インスタンスは保持されます)

そのため各ページの Fragment 内で ViewModel を取得するときに、以下のように ViewModelProviders.of() にその Fragment のインスタンスを指定してしまうと、 再度そのページが必要になったときに ViewModel も新しく作り直されてしまいます。 class PageFragment : Fragment() { ... override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) val viewModel = ViewModelProviders.of(this) .get(PageViewModel::class.java) val position = arguments!!.getInt("position") Log.d("PageFragment", "$position : $viewModel") } companion object { fun newInstance(position: Int): PageFragment { return PageFragment().apply { arguments = Bundle().apply { putInt("position", position) } } } } } 例えば最初に 1ページ目が表示され、そこから3ページ目に移動して1ページ目に戻ってくると、ViewModel のインスタンスが新しくなってしまい以前のデータを使えません。

D/PageFragment: 0 : net.yanzm.sample.PageViewModel@2dc8dc7
D/PageFragment: 1 : net.yanzm.sample.PageViewModel@389f31d
D/PageFragment: 2 : net.yanzm.sample.PageViewModel@60a3f63
D/PageFragment: 3 : net.yanzm.sample.PageViewModel@ad7a419
D/PageFragment: 0 : net.yanzm.sample.PageViewModel@8d76ebf ← 2dc8dc7 ではなく、別のインスタンスが来てしまう

そこで ViewModelProviders.of() に ViewPager を持つ Activity を指定すると、ViewModel のインスタンスが Activity にひも付くので、 ViewPager のページ移動で Fragment が作り直されても ViewModel は以前のインスタンスが取れるようになります。 val viewModel = ViewModelProviders.of(activity) .get(PageViewModel::class.java) しかし別の問題が起こります。各ページが同じ PageFragment クラスなので、今度は全部のページで同じ ViewModel のインスタンスが来てしまいます。

D/PageFragment: 0 : net.yanzm.focussample.PageViewModel@a704306
D/PageFragment: 1 : net.yanzm.focussample.PageViewModel@a704306
D/PageFragment: 2 : net.yanzm.focussample.PageViewModel@a704306
D/PageFragment: 3 : net.yanzm.focussample.PageViewModel@a704306
D/PageFragment: 0 : net.yanzm.focussample.PageViewModel@a704306

各ページで別の ViewModel インスタンスを使いたいので、ViewModel のインスタンスを取得するときにキーを指定するようにします。 val position = arguments!!.getInt("position") val viewModel = ViewModelProviders.of(activity) .get(position.toString(), PageViewModel::class.java) // position をキーとして指定 各ページで別の ViewModel インスタンスを使い、ViewPager のページ移動で Fragment が作り直されても ViewModel は以前のインスタンスが取れるようになりました。

D/PageFragment: 0 : net.yanzm.focussample.PageViewModel@a704306
D/PageFragment: 1 : net.yanzm.focussample.PageViewModel@2dc8dc7
D/PageFragment: 2 : net.yanzm.focussample.PageViewModel@a052ff4
D/PageFragment: 3 : net.yanzm.focussample.PageViewModel@389f31d
D/PageFragment: 0 : net.yanzm.focussample.PageViewModel@a704306 ← 同じ a704306 が返ってきている。


ViewPager を持っているのが Fragment の場合は、parentFragment を指定すれば ViewModel を ViewPager を持っている Fragment にひも付けることができます。 val position = arguments!!.getInt("position") val viewModel = ViewModelProviders.of(parentFragment!!) .get(position.toString(), PageViewModel::class.java) // position をキーとして指定



2018年4月11日水曜日

reified を使って lazy で intent から extra を取り出す部分を共通化する

lazy で intent から extra を取り出す部分を reified を使って Activity の拡張関数として定義してみました。 inline fun <reified T> Activity.lazyWithExtras(key: String): Lazy<T> { return lazy { intent.extras.get(key) as T } } class ProfileActivity : AppCompatActivity() { private val name: String by lazyWithExtras(EXTRAS_NAME) private val age: Int by lazyWithExtras(EXTRAS_AGE) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val textView = TextView(this) setContentView(textView) textView.text = "$name $age" } companion object { private const val EXTRAS_NAME = "name" private const val EXTRAS_AGE = "age" fun createIntent(context: Context, name: String, age: Int): Intent { return Intent(context, ProfileActivity::class.java).apply { putExtra(EXTRAS_NAME, name) putExtra(EXTRAS_AGE, age) } } } } Fragment では arguments は NonNull 前提としました inline fun <reified T> Fragment.lazyWithArgs(key: String): Lazy<T> { return lazy { arguments!!.get(key) as T } } class ProfileFragment : Fragment() { private val name: String by lazyWithArgs(ARGS_NAME) private val age: Int by lazyWithArgs(ARGS_AGE) override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) textView.text = "$name $age" } companion object { private const val ARGS_NAME = "name" private const val ARGS_AGE = "age" fun newInstance(name: String, age: Int): ProfileFragment { return ProfileFragment().apply { arguments = Bundle().apply { putString(ARGS_NAME, name) putInt(ARGS_AGE, age) } } } } }

2018年4月10日火曜日

Activity のパッケージを変えるときは activity-alias で古い Intent に対応できる

アプリのリニューアルやリファクタリングで Activity のパッケージを変更したいことがあります。
このときに問題になるのが古いショートカット機能やウィジェットです。

例えばアプリのパッケージが com.example.app で、MainActivity のパッケージも com.example.app だとします。

古いショートカット機能で MainActivity を開くショートカットを作ると、それには次のような Intent がセットされています。 val intent = Intent(context, MainActivity::class.java) // この intent の component は // ComponentName("com.example.app", "com.example.app.MainActivity") この状態で MainActivity の場所を com.example.app.ui に変更してアプリをアップデートすると、ショートカットをタップしても MainActivity が起動しなくなってしまいます。
Intent の ComponentName に対応する "com.example.app.MainActivity" が無いからです。

そこで activity-alias を使って "com.example.app.MainActivity" が指定されたときは "com.example.app.ui.MainActivity" を指すように alias を定義します。

activity-alias-element

<activity-alias> タグは <application> タグの中で使います。

<activity-alias> の android:targetActivity でこの alias 先の Activity を指定します。この指定先の Activity は <activity-alias> よりも先(上)に <activity> で定義されている必要があります。 <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" package="com.example.app"> <application ...> <activity android:name=".ui.MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity-alias android:name=".MainActivity" android:targetActivity=".ui.MainActivity" /> </application> </manifest>