I use Kotlin so I wanted a Kotlin solution and not a Java solution.
So first I look carefully over @FrostRocket's solution as a starting point. The solution of @FrostRocket was useful for me.
In my case, I ignore all the Kotlin world. As well as I can only use OffsetDateTime and not the calendar class. In addition, I have never used coroutine before, so I have to read and study coroutines in Kotlin. Let me know if you find in my code some theoretical or practical error.
First in my extension package I add a new File : ContextExtention.kt
import android.app.DatePickerDialog
import android.app.TimePickerDialog
import android.content.Context
import android.text.format.DateFormat
import java.time.OffsetDateTime
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
suspend fun Context.openDateTimePicker(offsetDateTime: OffsetDateTime = OffsetDateTime.now()): OffsetDateTime =
suspendCoroutine { continuation ->
val dateSetListener = DatePickerDialog.OnDateSetListener { _, year, month, day ->
//month (0-11 for compatibility with Calendar#MONTH)
// day of the month (1-31, depending on month)
val timeSetListener = TimePickerDialog.OnTimeSetListener { _, hour, minute ->
offsetDateTime.let {
val newOffsetDateTime =
offsetDateTime.withHour(hour).withMinute(minute).withYear(year)
//withMonth from 1 (January) to 12 (December)
//so I have to adapt it.
.withMonth((month+1))
//withDayOfMonth from 1 to 28-31
//so no need adaptation
.withDayOfMonth(day)
continuation.resume(newOffsetDateTime)
}
}
TimePickerDialog(
this,
timeSetListener,
offsetDateTime.hour,
offsetDateTime.minute,
DateFormat.is24HourFormat(this)
).show()
}
//the initially selected month (0-11 for compatibility with Calendar#MONTH)
//when offsetDateTime.monthValue from 1 (January) to 12 (December)
//so I have to adapt it.
DatePickerDialog(
this,
dateSetListener,
offsetDateTime.year,
(offsetDateTime.monthValue-1),
offsetDateTime.dayOfMonth
).show()
}
So how can I run a suspended function? So you run a coroutine in a kotlinx.coroutines.Job, CoroutineScope made me remember about Grand Central Dispatcher in iPhone programming Where you have a list of quality of service.
In this case if you want to use the UI thread you have to use: Dispatchers.Main or Dispatchers.Main.immediate if you need better service.
import android.widget.TextView
import androidx.lifecycle.Observer
// the R import here
// the openDateTimePicker import here
// the TimeHelper (is text formatter) import here
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
class FooFragment : Fragment() {
private lateinit var viewModel: FooViewModel
private lateinit var dateButton: Button
private lateinit var dateText: TextView
private var datePickerJob : Job? = null
val uiScope = CoroutineScope(Dispatchers.Main)
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val root =
inflater.inflate(R.layout.foo_fragment, container, false)
dateButton = root.findViewById(R.id.textButtonChangeDate)
dateText = root.findViewById(R.id.textViewWeighingDateValue)
viewModel = ViewModelProvider(this).get(FooViewModel::class.java)
return root
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel.dateTimeText.observe(viewLifecycleOwner, Observer {
dateText.text = it.format(TimeHelper.formatter)
//the call to setNewDateTime will refresh this value
})
}
override fun onStart() {
super.onStart()
dateButton.setOnClickListener {
datePickerJob = uiScope.launch {
viewModel.setNewDateTime(context?.openDateTimePicker())
//setNewDateTime is a setValue in a MutableLiveData
}
}
}
override fun onStop() {
super.onStop()
dateButton.setOnClickListener(null)
datePickerJob?.cancel()
}
}