2017年7月11日火曜日

Kotlin メモ : firstOrNull()

Java public enum Fruit { APPLE(0), BANANA(1), PEACH(2), ORANGE(3); private final int value; Fruit(int value) { this.value = value; } @NonNull public static Fruit find(int value) { for (Fruit fruit : values()) { if (fruit.value == value) { return fruit; } } return APPLE; } } Kotlin 変換直後 enum class Fruit(private val value: Int) { APPLE(0), BANANA(1), PEACH(2), ORANGE(3); companion object { @JvmStatic fun find(value: Int): Fruit { for (fruit in values()) { if (fruit.value == value) { return fruit } } return APPLE } } } Kotlin with firstOrNull() enum class Fruit(private val value: Int) { APPLE(0), BANANA(1), PEACH(2), ORANGE(3); companion object { @JvmStatic fun find(value: Int): Fruit { return values().firstOrNull { it.value == value } ?: APPLE } } }

2017年7月7日金曜日

Kotlin スタートブックを読みました。



正誤表 http://www.ric.co.jp/book/error/error1039.html

Java をある程度できる人にとって、手っ取り早く Kotlin で読み書きできるようになれる入門書だと思いました。 第II部は特によかったです。

以下、もやもやした点と、正誤表に載っていなかった気になる点について書いておきます。





p46 4章 1.4
「原則として val を使用し、再代入を極力避けるべきです。」
とだけあってなぜなのか書かれていませんでした。わかる人にはわかりますが、わからない人にはわからないと思います。



p49 4章 2.1
イミュータブルという単語がいきなり出てきて特に説明もないので、この本を読む人はイミュータブルの意味を知っていて当然ということのようです。
カタカナでイミュータブルと書かれるともにょもにょしますが、まぁ決めの問題なので書く人の好きなようにすればよいと思います。こういう単語の翻訳は難しいですね。



p51 4章 2.1
trimMargin() は行頭の空白と続く marginPrefix を消してくれる関数ですが、本書での説明だと | だけ消すかのように読み取れました。
https://kotlinlang.org/docs/reference/basic-types.html
trimMargin() の marginPrefix のデフォルトが | であることも触れた方がよいと思いました。



p59 4章 3.2
myFavoriteInt() の実体が書かれていないので混乱しましたが、Int を返す関数のようです。
「条件分岐の部分に、定数以外を指定できるからです」
とあったので、条件式を書けるのかと思ってしまいました。そうではなく評価した結果の値が定数のように扱われるようでした。
つまり when (x) { 1 -> "one" x % 2 == 0 -> "odd" else -> "even" } のように書けるのかと思ってしまったのですが、以下のように書かないといけません。 val x = 4 when { x == 1 -> "one" x % 2 == 0 -> "odd" else -> "even" }



p243 15章 2
view_article.xml のルート View を RelativeLayout にしていますが、 これを FrameLayout を継承した ArticleView に追加するのは View 階層の無駄なので、ここでは ルートを <merge> にして ArticleView が RelativeLayout を継承する形にするのが適切だと思います。



p245 15章 2
そもそも lazy の例として、カスタムビューで子 View のインスタンスを取得するのは適切ではないと思います。
Java で書くときも以下のように final field にしてコンストラクタで初期化するようにしています。 public class ArticleView extends RelativeLayout { private final ImageView profileImageView; private final TextView titleView; private final TextView userNameTextView; public ArticleView(Context context) { this(context, null); } public ArticleView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public ArticleView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); LayoutInflater.from(context).inflate(R.layout.view_article, this); profileImageView = (ImageView) findViewById(R.id.profile_image_view); titleView = (TextView) findViewById(R.id.title_text_view); userNameTextView = (TextView) findViewById(R.id.user_name_text_view); } } これを Kotlin でもやるなら以下のようになって、そもそも lazy はいりません。 class ArticleView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : RelativeLayout(context, attrs, defStyleAttr) { private val profileImageView: ImageView private val titleView: TextView private val userNameTextView: TextView init { LayoutInflater.from(context).inflate(R.layout.view_article, this) profileImageView = findViewById(R.id.profile_image_view) as ImageView titleView = findViewById(R.id.title_text_view) as TextView userNameTextView = findViewById(R.id.user_name_text_view) as TextView } } Fragment で getArguments() から値を取り出す場合のほうが lazy の例として適切だと思います。



p247 15章 2
p253 15章 3
ArticleView のコンストラクタに applicationContext を渡していますが、よくないです。
理由は View のコンテキストに Application Context を渡すとテーマが適用されない に書きました。



p251 15章 3
私だったら Adapter が持つ List
は外部に公開しないようにするかな。



p274 16章
Context に拡張関数で toast() を生やす方法、Window を持たない Context (Application Context とか Service の Context とか)でうっかり呼び出すと落ちるから、個人的にはあんまり適切な例ではないと思っています。
Toast の Context は Application Context でも OK でした。最近 Toast 使わないからうっかりしてました。 Toast では Context をパッケージ名の取得と string とかのリソースの取得にしか使ってないので Window は関係ありませんでした。 Application Context を渡すと Window が無くて死ぬのは Dialog でした。




View のコンテキストに Application Context を渡すとテーマが適用されない

View のコンストラクタは Context を必要とします。
Context として以下のように Application Context を渡しているコードを見かけました。 public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Button textView = new Button(getApplicationContext()); } } これがなぜよくないのか説明します。

View に渡された Context はスタイル属性を取得するのに使われます。

View.java public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { this(context); final TypedArray a = context.obtainStyledAttributes( attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes); ... background = a.getDrawable(attr); ... View のスタイルは
  1. 1. XMLタグで指定された値(例 android:background="@drawable/button_bg")
  2. 2. style で指定された値(例 style="@style/MyButtonStyle)
  3. 3. テーマで指定された値(例 テーマの中で @style/MyButtonStyle
の優先度で適用されます。

このことを踏まえて obtainStyledAttributes() の中身をみると

Context.java public final TypedArray obtainStyledAttributes( AttributeSet set, @StyleableRes int[] attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes) { return getTheme().obtainStyledAttributes( set, attrs, defStyleAttr, defStyleRes); } getTheme() で Theme を取得し、Theme の obtainStyledAttributes() を呼んでいます。
つまり、3. では Context が持っているテーマ情報を使っているのです。

Application Context を渡してはいけない理由がわかったでしょうか。

Activity ごとにテーマを指定できるのに、Application Context を渡してしまうと、Activity のテーマが全く利用されません。
実際にやってみましょう。

Application には Theme.AppCompat(黒系)、MainActivity には Theme.AppCompat.Light(白系)を指定します。 <manifest ...> <application ... android:theme="@style/Theme.AppCompat"> <activity android:name=".MainActivity" android:theme="@style/Theme.AppCompat.Light"> ... </activity> </application> </manifest> public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button button1 = new Button(getApplicationContext()); button1.setText("Application Context"); Button button2 = new Button(this); button1.setText("Activity Context"); LinearLayout ll = (LinearLayout) findViewById(R.id.container); ll.addView(button1); ll.addView(button2); } }



画面のテーマは Theme.AppCompat.Light (白系)なのに、Application Context を渡した上のボタンは Theme.AppCompat(黒系)の色になってしまっています。



Context が持つテーマが利用されるという仕組みは、v7 AppCompat Support Library でも使われています。 AppCompatTextView を見てみましょう。 public class AppCompatTextView extends TextView implements TintableBackgroundView { ... public AppCompatTextView(Context context, AttributeSet attrs, int defStyleAttr) { super(TintContextWrapper.wrap(context), attrs, defStyleAttr); TintContextWrapper でラップした Context を TextView に渡しています。
これにより、colorAccent の指定色によって自動でテキストカーソルなどが tint されます。