2018年4月21日土曜日

kotlin.math を使う

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

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

試しに全部使ってみました。基本 Float を使ってますが Double も用意されています。
  1. package net.yanzm.sample  
  2.   
  3. import kotlin.math.*  
  4.   
  5. fun main(args: Array<string>) {  
  6.   
  7.     println(E)  // 2.718281828459045  
  8.     println(PI) // 3.141592653589793  
  9.   
  10.     // unit in the last place  
  11.     println(PI.ulp) // 4.440892098500626E-16  
  12.   
  13.     // the remainder of division  
  14.     println(10f.IEEErem(3f)) // 1.0  
  15.   
  16.     //  
  17.     // sign and absolute value  
  18.     //  
  19.   
  20.     println(10.sign)    // 1  
  21.     println((-10).sign) // -1  
  22.   
  23.     println(sign(10f))  // 1.0  
  24.     println(sign(-10f)) // -1.0  
  25.   
  26.     println((1.1f).withSign(-2f))  // -1.1  
  27.     println((1.1f).withSign(2f))   // 1.1  
  28.     println((-1.1f).withSign(-2f)) // -1.1  
  29.     println((-1.1f).withSign(2f))  // 1.1  
  30.   
  31.     println(10.absoluteValue)     // 10  
  32.     println((-10).absoluteValue)  // 10  
  33.   
  34.     println(abs(10))   // 10  
  35.     println(abs(-10))  // 10  
  36.   
  37.     //  
  38.     // rounding  
  39.     //  
  40.   
  41.     println(ceil(1.1f))  // 2.0  
  42.     println(ceil(1.5f))  // 2.0  
  43.     println(ceil(-1.1f)) // -1.0  
  44.     println(ceil(-1.5f)) // -1.0  
  45.   
  46.     println(floor(1.1f))  // 1.0  
  47.     println(floor(1.5f))  // 1.0  
  48.     println(floor(-1.1f)) // -2.0  
  49.     println(floor(-1.5f)) // -2.0  
  50.   
  51.     println(truncate(1.1f))  // 1.0  
  52.     println(truncate(1.5f))  // 1.0  
  53.     println(truncate(-1.1f)) // -1.0  
  54.     println(truncate(-1.5f)) // -1.0  
  55.   
  56.     println(round(1.1f))  // 1.0  
  57.     println(round(1.5f))  // 2.0  
  58.     println(round(-1.1f)) // -1.0  
  59.     println(round(-1.5f)) // -2.0  
  60.   
  61.     println((1.1f).roundToInt())  // 1  
  62.     println((1.5f).roundToInt())  // 2  
  63.     println((-1.1f).roundToInt()) // -1  
  64.     println((-1.5f).roundToInt()) // -2  
  65.   
  66.     println((1.1f).roundToLong())  // 1L  
  67.     println((1.5f).roundToLong())  // 2L  
  68.     println((-1.1f).roundToLong()) // -1L  
  69.     println((-1.5f).roundToLong()) // -2L  
  70.   
  71.     //  
  72.     // trigonometric  
  73.     //  
  74.   
  75.     println(sin(PI / 2))  // 1.0  
  76.     println(asin(1f))  // 1.5707964  
  77.   
  78.     println(cos(PI))     // -1.0  
  79.     println(acos(-1f)) // 3.1415927  
  80.   
  81.     println(tan(PI / 4))      // 0.9999999999999999  
  82.     println(atan(1f))         // 0.7853982  
  83.     println(atan2(5f, 5f)) // 0.7853982  
  84.   
  85.     //  
  86.     // hyperbolic  
  87.     //  
  88.   
  89.     println(sinh(0.8813736f)) // 1.0  
  90.     println(asinh(1f)) // 0.8813736  
  91.   
  92.     println(cosh(0f))  // 1.0  
  93.     println(acosh(1f)) // 0.0  
  94.   
  95.     println(tanh(0.54930615f))  // 0.5  
  96.     println(atanh(0.5f))        // 0.54930615  
  97.   
  98.     //  
  99.     // exponentiation and power  
  100.     //  
  101.   
  102.     println(exp(0f)) // 1.0  
  103.     println(exp(1f)) // 2.7182817  
  104.   
  105.     println(expm1(0f)) // 0.0  
  106.     println(expm1(1f)) // 1.7182817  
  107.   
  108.     println(2f.pow(2)) // 4.0  
  109.   
  110.     println(sqrt(4f))  // 2.0  
  111.   
  112.     // sqrt(x^2 + y^2)  
  113.     println(hypot(3f, 4f)) // 5.0  
  114.   
  115.     //  
  116.     // logarithmic  
  117.     //  
  118.   
  119.     println(ln(E))   // 1.0  
  120.     println(ln(1f)) // 0.0  
  121.   
  122.     // ln(x + 1)  
  123.     println(ln1p(E - 1)) // 1.0  
  124.     println(ln1p(0f))    // 0.0  
  125.   
  126.     println(log(10f, 10f)) // 1.0  
  127.     println(log(1f, 10f))  // 0.0  
  128.   
  129.     println(log(2f, 2f))  // 1.0  
  130.     println(log(1f, 2f))  // 0.0  
  131.   
  132.     // log(x, 10)  
  133.     println(log10(10f)) // 1.0  
  134.     println(log10(1f))  // 0.0  
  135.   
  136.     // log(x, 2)  
  137.     println(log2(2f))  // 1.0  
  138.     println(log2(1f))  // 0.0  
  139.   
  140.     //  
  141.     // max, min  
  142.     //  
  143.   
  144.     println(max(12))   // 2  
  145.     println(max(1f, 2f)) // 2.0  
  146.   
  147.     println(min(12))   // 1  
  148.     println(min(1f, 2f)) // 1.0  
  149.   
  150.     //  
  151.     // nearest  
  152.     //  
  153.   
  154.     println((1.1f).nextDown())  // 1.0999999  
  155.     println((-1.1f).nextDown()) // -1.1000001  
  156.   
  157.     println((1.1f).nextUp())    // 1.1000001  
  158.     println((-1.1f).nextUp())   // -1.0999999  
  159.   
  160.     println((1.1f).nextTowards(0f))  // 1.0999999  
  161.     println((-1.1f).nextTowards(0f)) // -1.0999999  
  162.   
  163. }  
  164. </string>  



2018年4月12日木曜日

ViewPager + Fragment で AAC の ViewModel を使う

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

そのため各ページの Fragment 内で ViewModel を取得するときに、以下のように ViewModelProviders.of() にその Fragment のインスタンスを指定してしまうと、 再度そのページが必要になったときに ViewModel も新しく作り直されてしまいます。
  1. class PageFragment : Fragment() {  
  2.   
  3.     ...  
  4.   
  5.     override fun onActivityCreated(savedInstanceState: Bundle?) {  
  6.         super.onActivityCreated(savedInstanceState)  
  7.   
  8.         val viewModel = ViewModelProviders.of(this)  
  9.                 .get(PageViewModel::class.java)  
  10.   
  11.         val position = arguments!!.getInt("position")  
  12.   
  13.         Log.d("PageFragment""$position : $viewModel")  
  14.     }  
  15.   
  16.     companion object {  
  17.         fun newInstance(position: Int): PageFragment {  
  18.             return PageFragment().apply {  
  19.                 arguments = Bundle().apply {  
  20.                     putInt("position", position)  
  21.                 }  
  22.             }  
  23.         }  
  24.     }  
  25. }  
例えば最初に 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 は以前のインスタンスが取れるようになります。
  1. val viewModel = ViewModelProviders.of(activity)  
  2.         .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 のインスタンスを取得するときにキーを指定するようにします。
  1. val position = arguments!!.getInt("position")  
  2.   
  3. val viewModel = ViewModelProviders.of(activity)  
  4.         .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 にひも付けることができます。
  1. val position = arguments!!.getInt("position")  
  2.   
  3. val viewModel = ViewModelProviders.of(parentFragment!!)  
  4.         .get(position.toString(), PageViewModel::class.java) // position をキーとして指定  




2018年4月11日水曜日

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

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

2018年4月10日火曜日

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

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

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

古いショートカット機能で MainActivity を開くショートカットを作ると、それには次のような Intent がセットされています。
  1. val intent = Intent(context, MainActivity::class.java)  
  2. // この intent の component は  
  3. // 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> で定義されている必要があります。
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     xmlns:tools="http://schemas.android.com/tools"  
  4.     package="com.example.app">  
  5.   
  6.     <application ...>  
  7.   
  8.         <activity  
  9.             android:name=".ui.MainActivity" />  
  10.   
  11.         <activity-alias  
  12.             android:name=".MainActivity"  
  13.             android:targetActivity=".ui.MainActivity" >  
  14.             <intent-filter>  
  15.                 <action android:name="android.intent.action.MAIN" />  
  16.                 <category android:name="android.intent.category.LAUNCHER" />  
  17.             </intent-filter>  
  18.         </activity-alias>  
  19.   
  20.     </application>  
  21.   
  22. </manifest>