Home > Mobile >  How to show and save contact name in Android Fragment
How to show and save contact name in Android Fragment

Time:04-24

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.

  • Related