I am a newcomer to Android development and Kotlin. I encounter a problem when I read a Android programming book. The book uses startActivityForResult() and onActivityResult() which are deprecated, so I use registerForActivityResult() instead, then the problem occurs...
The problem is that I get a contact name back from the contacts app, but can't show it on the button in the fragment or save it to the db.
I debug the program and find the cause may be the execute order after returning from contacts app. The order is: onCreate -> onCreateView -> onViewCreated -> ActivityResultCallback -> crimeLiveData's Observer. In ActivityResultCallback I get a new crime which is not the one when I enter the CrimeFragment, so save it to db have no effect, but can set the suspectButton text. In crimeLiveData's Observer, I get the crime I want, but without the contact name or with the contact name unchanged. After updateUI(), the suspectButton text is overridden and back to original value. So the result is that the fragment's view is unchanged although I have pressed the suspectButton and changed the contact name.
If you know how to show the contact name I selected and save it, please tell me, thank you very much!!!
Below is the code:
// The fragment where I have a suspectButton to show the contact name
class CrimeFragment : Fragment() {
private lateinit var solvedCheckBox: CheckBox
private lateinit var reportButton: Button
// the button to show contact name
private lateinit var suspectButton: Button
private lateinit var dateButton: Button
private lateinit var crime: Crime
private lateinit var titleField: EditText
// register a call back for returning from the contacts app
private val resultLauncher: ActivityResultLauncher<Intent> = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == Activity.RESULT_OK && it.data != null) {
val contactUri: Uri? = it.data?.data
val queryFields = arrayOf(ContactsContract.Contacts.DISPLAY_NAME)
val cursor = contactUri?.let { uri ->
requireActivity().contentResolver.query(
uri, queryFields, null, null, null)
}
// Get the contact name, save it to db, set the button text
cursor?.use {
if (it.count != 0) {
it.moveToFirst()
val suspect = it.getString(0)
crime.suspect = suspect
crimeDetailViewModel.saveCrime(crime)
suspectButton.text = suspect
}
}
}
}
private val crimeDetailViewModel: CrimeDetailViewModel by lazy {
ViewModelProvider(this).get(CrimeDetailViewModel::class.java)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
crime = Crime()
val crimeId: UUID = arguments?.getSerializable(ARG_CRIME_ID) as UUID
crimeDetailViewModel.loadCrime(crimeId)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_crime, container, false)
titleField = view.findViewById(R.id.crime_title) as EditText
dateButton = view.findViewById(R.id.crime_date) as Button
solvedCheckBox = view.findViewById(R.id.crime_solved) as CheckBox
reportButton = view.findViewById(R.id.crime_report) as Button
suspectButton = view.findViewById(R.id.crime_suspect) as Button
return view
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
crimeDetailViewModel.crimeLiveData.observe(viewLifecycleOwner) { crime ->
crime?.let {
this.crime = crime
updateUI()
}
}
}
private fun updateUI() {
titleField.setText(crime.title)
dateButton.text = crime.date.toString()
solvedCheckBox.apply {
isChecked = crime.isSolved
jumpDrawablesToCurrentState()
}
if (crime.suspect.isNotEmpty()) {
suspectButton.text = crime.suspect
}
}
private fun getCrimeReport(): String {
val solvedString = if (crime.isSolved) getString(R.string.crime_report_solved) else getString(R.string.crime_report_unsolved)
val dateString = DateFormat.format(DATE_FORMAT, crime.date).toString()
val suspect = if (crime.suspect.isBlank()) getString(R.string.crime_report_no_suspect) else getString(R.string.crime_report_subject)
return getString(R.string.crime_report, crime.title, dateString, solvedString, suspect)
}
override fun onStart() {
super.onStart()
val titleWatcher = object : TextWatcher {
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
// left blank
}
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
crime.title = p0.toString()
}
override fun afterTextChanged(p0: Editable?) {
// left blank
}
}
titleField.addTextChangedListener(titleWatcher)
solvedCheckBox.apply {
setOnCheckedChangeListener { _, isChecked ->
crime.isSolved = isChecked
}
}
dateButton.setOnClickListener {
DatePickerFragment.newInstance(crime.date).apply {
show([email protected], DIALOG_DATE)
}
}
reportButton.setOnClickListener {
Intent(Intent.ACTION_SEND).apply {
type= "text/plain"
putExtra(Intent.EXTRA_TEXT, getCrimeReport())
putExtra(Intent.EXTRA_SUBJECT, getString(R.string.crime_report_subject))
}.also {
intent ->
val chooserIntent = Intent.createChooser(intent, getString(R.string.send_report))
startActivity(chooserIntent)
}
}
suspectButton.apply {
val pickContactIntent = Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI)
setOnClickListener {
resultLauncher.launch(pickContactIntent)
}
}
parentFragmentManager.setFragmentResultListener(REQUEST_KEY, viewLifecycleOwner) { requestKey, result ->
if (requestKey == DIALOG_DATE) {
crime.date = result.getSerializable(ARG_DATE) as Date
updateUI()
}
}
}
override fun onStop() {
super.onStop()
crimeDetailViewModel.saveCrime(crime)
}
companion object {
fun newInstance(crimeId: UUID): CrimeFragment {
val args = Bundle().apply {
putSerializable(ARG_CRIME_ID, crimeId)
}
return CrimeFragment().apply {
arguments = args
}
}
}
}
// ViewModel to the fragment
class CrimeDetailViewModel() : ViewModel() {
private val crimeRepository = CrimeRepository.get()
private val crimeIdLiveData = MutableLiveData<UUID>()
var crimeLiveData: LiveData<Crime?> = Transformations.switchMap(crimeIdLiveData) { crimeId ->
crimeRepository.getCrime(crimeId)
}
fun loadCrime(crimeId: UUID) {
crimeIdLiveData.value = crimeId
}
fun saveCrime(crime: Crime) {
crimeRepository.updateCrime(crime)
}
}
// Crime entity
@Entity
data class Crime(
@PrimaryKey val id: UUID = UUID.randomUUID(),
var title: String = "",
var date: Date = Date(),
var isSolved: Boolean = false,
var requiresPolice: Boolean = false,
var suspect: String = ""
)
// layout
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="16dp">
<TextView
style="?android:listSeparatorTextViewStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/crime_title_lable"/>
<EditText
android:id="@ id/crime_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/crime_title_hint"/>
<TextView
style="?android:listSeparatorTextViewStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/crime_details_label"/>
<Button
android:id="@ id/crime_date"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:text="Wed Nov 14 11:56 EST 2018"/>
<CheckBox
android:id="@ id/crime_solved"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/crime_solved_label"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/crime_suspect_text"
android:id="@ id/crime_suspect"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/crime_report_text"
android:id="@ id/crime_report"/>
</LinearLayout>
CodePudding user response:
I found a solution to this problem. Due to the execution order of ActivityResultCallback and crimeLiveData's Observer, below code in ActivityResultCallback has no effect:
crime.suspect = suspect
crimeDetailViewModel.saveCrime(crime)
suspectButton.text = suspect
so I change it to this:
suspectButton.text = suspect
and the undateUI() changed from:
if (crime.suspect.isNotEmpty()) {
suspectButton.text = crime.suspect
}
to this:
if (suspectButton.text.toString() != getString(R.string.crime_suspect_text)) {
crime.suspect = suspectButton.text.toString()
} else if (crime.suspect.isNotEmpty()) {
suspectButton.text = crime.suspect
}
Now the suspectButton can reflect the change I make. When leaving this fragment, the change saves, problem sovled.