如何在 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 可以反映我所做的更改。离开这个片段时,更改保存,问题解决。
我是 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 可以反映我所做的更改。离开这个片段时,更改保存,问题解决。