Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.
The Authoritative Guide to Android Programming, Chapter 15, uses implicit Intents to send short messages to Crime suspects.
Explicit Intent — Specifies the activity class to start, and the operating system is responsible for launching it. Implicit intent — Describes the task to be completed, and the operating system finds the appropriate application in which to launch the activity.
1. Add button parts
Start by adding two complaint buttons to the CrimeFragment layout.
<string name="crime_suspect_text">Choose Suspect</string> <string name="crime_report_text">Send Crime Report</string> <? The XML version = "1.0" encoding = "utf-8"? > <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" ... <CheckBox ... /> <Button android:id="@+id/crime_suspect" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/crime_suspect_text" /> <Button android:id="@+id/crime_report" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/crime_report_text" /> </LinearLayout>Copy the code
2. Add suspect information to the model layer
Add member variables to crime.kt.
@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 = ""
)
Copy the code
Open crimeDatabase. kt and change the database version from 1 to 2.
@Database(entities = [Crime::class], version = 2, exportSchema = false) @TypeConverters(CrimeTypeConverters::class) abstract class CrimeDatabase : RoomDatabase() { abstract fun crimeDao(): CrimeDao} val migration_1_2 = object :Migration(1,2){override fun migrate(database: SupportSQLiteDatabase) { database.execSQL( "ALTER TABLE Crime ADD COLUMN suspect TEXT NOT NULL DEFAULT ''" ) } }Copy the code
To create a Migration object to update the database, the Migration class constructor takes two parameters, the first is the previous version of the database, and the second is the version to which it was migrated.
Open crimerepository.kt and add Migration to Room when you create the CrimeDatabase.
class CrimeRepository private constructor(context: Context) {
private val database: CrimeDatabase = Room.databaseBuilder(
context.applicationContext,
CrimeDatabase::class.java,
DATABASE_NAME
).addMigrations(migration_1_2).build()
...
}
Copy the code
It is important to provide migration for database transformation. If not, Room will delete the old version of the database and create a new version of the database. That means all the data will be lost, and that’s not good.
Use formatted strings
The next step is to use a formatted string with placeholders that can be replaced at run time of the application.
Add string resources to string.xml. Special strings such as %1$s and %2$s are placeholders that accept string arguments.
<string name="crime_report">%1s! The Crime was discovered on 2%s.%3s,and %4s</string> <string name="crime_report_solved">The case is solved</string> <string name="crime_report_unsolved">The case is not solved</string> <string name="crime_report_no_suspect">there is no suspect</string> <string name="crime_report_suspect">the suspect is %s.</string> <string name="crime_report_subject">CriminalIntent Crime Report</string> <string name="send_report">Send crime report via</string>Copy the code
In crimefragment.kt, add the getCrimeReport() function:
private const val DATE_FORMAT = "EEE, MMM, dd" class CrimeFragment : Fragment(), DatePickerFragment.Callbacks { ... private fun getCrimeReport(): String { val solvedStr = if (mCrime.isSolved) { getString(R.string.crime_report_solved) } else { getString(R.string.crime_report_unsolved) } val dateStr = android.text.format.DateFormat.format(DATE_FORMAT, mCrime.date).toString() var suspect = if (mCrime.suspect.isBlank()) { getString(R.string.crime_report_no_suspect) } else { getString(R.string.crime_report_suspect, mCrime.suspect) } return String.format(resources.getString(R.string.crime_report), mCrime.title, dateStr, solvedStr, suspect) } }Copy the code
Use an implicit intent
What constitutes an implicit intent:
-
The operation to perform
It is usually represented as a constant in the Intent class. For example, the URL is intent.action_view; The email uses intent.action_send.
The Intent to introduce the official address: developer.android.com/reference/a…
-
The location of data to be accessed
For example, the URL of a web page, the URI of a file, or a content URI that points to a record in a ContentProvider.
-
The data type involved in the operation
Refers to a data type in MIME form, such as text/ HTML or audio/mpeg3.
-
Optional classes
Developer.android.com/guide/compo…
Note that explicit intents can also use the action and data parts of implicit intents. This is equivalent to asking a particular activity to do a particular thing.
The next step is to use an implicit intent to send a message. Add a click event to the button that was sent in the CrimeFragment:
mBinding.btnCrimeReport.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 -> startActivity(intent) }
}
Copy the code
The message content and subject are appended to the Intent as extra. Note that the extra messages use constants defined in the Intent class.
Click effect:
Create another implicit intent that lets the user select a suspect from the contacts app.
. class CrimeFragment : Fragment(), DatePickerFragment.Callbacks { ... val startForResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { if (it.resultCode == Activity.RESULT_OK) { val contactUri: Uri = it.data? .data!! val queryFilds = arrayOf(ContactsContract.Contacts.DISPLAY_NAME) val cursor = requireActivity().contentResolver.query(contactUri,queryFilds,null, null,null) cursor.use { if (it? .count == 0){ return@use } it? .moveToFirst() val suspect = it? .getString(0) mCrime.suspect = suspect!! crimeDetailViewModel.saveCrime(mCrime) mBinding.btnCrimeSuspect.text = suspect } } } ... mBinding.btnCrimeSuspect.apply { val pickContactIntent = Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI) setOnClickListener { startForResult.launch(pickContactIntent) } } ... private fun updateUI() { ... if (mCrime.suspect.isNotEmpty()){ mBinding.btnCrimeSuspect.text = mCrime.suspect } } ... }Copy the code
Here will involve a knowledge point, it is about one of the four main components of the android content provider, details refer to: developer.android.com/guide/topic…
The second implicit intent above risks crashing in case the user doesn’t have a contact app on their phone. The solution at this time is to self-check through the PackageManager class in the operating system.
mBinding.btnCrimeSuspect.apply {
val pickContactIntent =
Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI)
setOnClickListener {
startForResult.launch(pickContactIntent)
}
val packageManager: PackageManager = requireActivity().packageManager
val resolvedActivity: ResolveInfo? =
packageManager.resolveActivity(pickContactIntent, PackageManager.MATCH_DEFAULT_ONLY)
if (resolvedActivity == null) {
isEnabled = false
}
}
Copy the code
The PackageManager knows which components and activities are installed on the Android device. Call resolveActivity(Intent, Int) to find an activity that matches the given Intent task. The flag MATCH_DEFAULT_ONLY restricts the search for activities with the CATEGORY_DEFAULT flag.
If the target is found, it returns ResolveInfo to tell us which activity was found. If you can’t find it, you must disable the suspect button or the app will crash.
If you want to validate filters, feel free to add individual categories of intEnts.
Another implicit intent
A new button will allow users to call suspects directly.
Here will involve a runtime permissions request, about the knowledge point of reference: developer.android.com/training/pe… You can also use the permission request library wrapped by someone else, which is very convenient to use. For example: github.com/guolindev/P…
Update this challenge next time! Next time! Make up!
Sixth, other
CriminalIntent project Demo address: github.com/visiongem/A…
🌈 follow me ac~ ❤️
Public account: Ni K Ni K