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