Android
AndroidDay 21

Android の DatePickerDialog で選択許可する年月日を制限すると不具合に遭遇したでござるの巻

目的

Android の DatePickerDialog でユーザーが自身の誕生日を選択するシチュエーションにおいては、生前や未来など不正な値を設定できないアーキテクチャにしておきたいです。

実践

Stack Overflow の回答を参考に、本日(2018年1月13日)より先の年月日を選択できないようにします。

不具合 :one:

Issue Tracker にも起票されていますが Android 6.0 (Marshmallow) 端末では、年月日の選択範囲に上限または下限の制約を加えると、ダイアログのタイトルが勝手に表示されるバグがあります :sob:

解決策

ダイアログのタイトルに空文字をセットすれば非表示にできると Stack Overflow に回答がありました :ok_woman:

不具合 :two:

Android 5.0 (Lollipop) 端末では、許容範囲外の日付をタップで選択できるというバグがあります :sob:
Issue Tracker の情報によると Android 5.1 では修正されているようです。

解決策

OS バージョンが 5.0 の場合は、ダイアログに Holo テーマをセットして NumberPicker による選択方式にしてしまう案を思い付きました :bulb:(デザインの統一が阻まれそうですが)。
こちらは制限した範囲の年月日が選択不可になったので(ソフトキーからの数値入力でも)バグは回避できそうです。

ある程度のバグを塞いだであろう実装

Kotlin で書いてみました :writing_hand:

DatePickerDialogFragment.kt
import android.app.AlertDialog
import android.app.DatePickerDialog
import android.app.DatePickerDialog.OnDateSetListener
import android.app.Dialog
import android.os.Build
import android.os.Bundle
import android.support.v4.app.DialogFragment
import java.util.*

/**
 * 年月日を選択するダイアログ
 */
class DatePickerDialogFragment : DialogFragment() {

    companion object {
        fun newInstance(): DatePickerDialogFragment = DatePickerDialogFragment()
    }

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        val calendar = Calendar.getInstance()
        return DatePickerDialog(
                context,
                // Lollipop の場合は Holo テーマのダイアログにする
                if (Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP) AlertDialog.THEME_HOLO_LIGHT else theme,
                OnDateSetListener { view, year, month, dayOfMonth -> },
                calendar.get(Calendar.YEAR),
                calendar.get(Calendar.MONTH),
                calendar.get(Calendar.DAY_OF_MONTH))
                .also {
                    // 選択可能な年月日の上限を指定します
                    it.datePicker.maxDate = calendar.timeInMillis
                    // タイトルが勝手に表示されるのを防ぐために空文字をセットします
                    it.setTitle("")
                }
    }
}
MainActivity.kt
import android.os.Bundle
import android.support.v7.app.AppCompatActivity

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        DatePickerDialogFragment
                .newInstance()
                .show(supportFragmentManager, DatePickerDialogFragment::class.java.simpleName)
    }
}

実行結果

Android 8.0 (Oreo) のエミュレータで表示しました :calendar: