如何在 Android 片段中显示和保存联系人姓名

How to show and save contact name in Android Fragment

我是 Android 开发和 Kotlin 的新手。我在阅读一本 Android 编程书籍时遇到了一个问题。书上使用的startActivityForResult()和onActivityResult()已经过时了,所以我改用registerForActivityResult(),然后问题就出现了...

问题是我从联系人应用程序中获取了联系人姓名,但无法在片段中的按钮上显示它或将其保存到数据库中。

调试程序发现可能是通讯录app返回后的执行顺序问题。顺序是:onCreate -> onCreateView -> onViewCreated -> ActivityResultCallback -> crimeLiveData的Observer。在 ActivityResultCallback 中,我得到了一个新的犯罪,这不是我进入 CrimeFragment 时的犯罪,所以将其保存到 db 没有任何效果,但可以设置 suspectButton 文本。在 crimeLiveData 的 Observer 中,我得到了我想要的犯罪,但没有联系人姓名或联系人姓名不变。在 updateUI() 之后,suspectButton 文本被覆盖并返回到原始值。所以结果是片段的视图没有改变,尽管我已经按下了 suspectButton 并更改了联系人姓名。

如果你知道如何显示我选择的联系人姓名并保存,请告诉我,非常感谢!!!

代码如下:

// 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(this@CrimeFragment.parentFragmentManager, 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>

我找到了解决这个问题的办法。由于ActivityResultCallback和crimeLiveData的Observer的执行顺序,ActivityResultCallback中的以下代码没有效果:

crime.suspect = suspect
crimeDetailViewModel.saveCrime(crime)
suspectButton.text = suspect

所以我改成这样:

suspectButton.text = suspect

undateUI() 更改自:

if (crime.suspect.isNotEmpty()) {
    suspectButton.text = crime.suspect
}

对此:

if (suspectButton.text.toString() != getString(R.string.crime_suspect_text)) {
    crime.suspect = suspectButton.text.toString()
} else if (crime.suspect.isNotEmpty()) {
    suspectButton.text = crime.suspect
}

现在 suspectButton 可以反映我所做的更改。离开这个片段时,更改保存,问题解决。