Day81 of #100DaysOfCode
Hii folks 🙌
Today I will be continuing the same pathway in which we will implement send order feature.
Unit 3: Navigation
Pathway 3: Advanced navigation app Examples
https://developer.android.com/courses/android-basics-kotlin/course
We will implement the toast
message which will appear when we tap on the send order button on the SummaryFragment
,
It would be a more useful experience if the order could be sent out from the app. That way, the user can share the cupcake order information with an email app on the device, allowing the order to be emailed to the cupcake shop.
To implement this feature, we’ll be using these strings that are already in our strings.xml
file.
<string name="new_cupcake_order">New Cupcake Order</string>
<string name="order_details">Quantity: %1$s cupcakes \n Flavor: %2$s \nPickup date: %3$s \n Total: %4$s \n\n Thank you!</string>
order_details
is a string resource with 4 different format arguments in it, which are placeholders for the actual quantity of cupcakes, desired flavor, desired pickup date, and total price. The arguments are numbered from 1 to 4 with the syntax %1
to %4
. The type of argument is also specified ($s
means a string is expected here).
Quantity: 12 cupcakes
Flavor: Chocolate
Pickup date: Sat Dec 12
Total: $24.00Thank you!
<string name="subtotal_price">Subtotal %s</string>
<string name="total_price">Total %s</string>
where %s
is a placeholder for the formatted price string.
- In
SummaryFragment.kt
modify thesendOrder()
method. Remove the existingToast
message.
fun sendOrder() {
}
- Within the
sendOrder()
method, construct the order summary text. Create the formattedorder_details
string by getting the order quantity, flavor, date, and price from the shared view model.
val orderSummary = getString(
R.string.order_details,
sharedViewModel.quantity.value.toString(),
sharedViewModel.flavor.value.toString(),
sharedViewModel.date.value.toString(),
sharedViewModel.price.value.toString()
)
- Still within the
sendOrder()
method, create an implicit intent for sharing the order to another app. See the documentation for how to create an email intent. SpecifyIntent.ACTION_SEND
for the intent action, set type to"text/plain"
and include intent extras for the email subject (Intent.EXTRA_SUBJECT
) and email body (Intent.EXTRA_TEXT
). Importandroid.content.Intent
if needed.
val intent = Intent(Intent.ACTION_SEND)
.setType("text/plain")
.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.new_cupcake_order))
.putExtra(Intent.EXTRA_TEXT, orderSummary)
- Since this is an implicit intent, we don’t need to know ahead of time which specific component or app will handle this intent. The user will decide which app they want to use to fulfill the intent. However, before launching an activity with this intent, check to see if there’s an app that could even handle it. This check will prevent the Cupcake app from crashing if there’s no app to handle the intent, making our code safer.
if (activity?.packageManager?.resolveActivity(intent, 0) != null) {
startActivity(intent)
}
Perform this check by accessing the PackageManager
, which has information about what app packages are installed on the device. The PackageManager
can be accessed via the fragment's activity
, as long as the activity
and packageManager
are not null. Call the PackageManager
's resolveActivity()
method with the intent we created. If the result is not null, then it is safe to call startActivity()
with our intent.
In testing different scenarios, we may notice a bug if we only have 1 cupcake. The order summary says 1 cupcakes, but in English, this is grammatically incorrect.
Instead, it should say 1 cupcake (no plural). If we want to choose whether the word cupcake or cupcakes is used based on the quantity value, then we can use something called quantity strings in Android. By declaring a plurals
resource, we can specify different string resources to use based on what the quantity is, for example in the singular or plural case.
- Add a
cupcakes
plurals resource in ourstrings.xml
file.
<plurals name="cupcakes">
<item quantity="one">%d cupcake</item>
<item quantity="other">%d cupcakes</item>
</plurals>
In the singular case (quantity="one"
), the singular string will be used. In all other cases (quantity="other"
), the plural string will be used. Note that instead of %s
which expects a string argument, %d
expects an integer argument, which we will pass in when we format the string.
In our Kotlin code, calling:
getQuantityString(R.plurals.cupcakes, 1, 1)
returns the string 1 cupcake
getQuantityString(R.plurals.cupcakes, 6, 6)
returns the string 6 cupcakes
getQuantityString(R.plurals.cupcakes, 0, 0)
returns the string 0 cupcakes
Note: When calling
getQuantityString()
, we need to pass in the quantity twice because the first quantity parameter is used to select the correct plural string. The second quantity parameter is used in the%d
placeholder of the actual string resource.
- Before going to our Kotlin code, update the
order_details
string resource instrings.xml
so that the plural version of cupcakes is no longer hardcoded into it.
<string name="order_details">Quantity: %1$s \n Flavor: %2$s \nPickup date: %3$s \n
Total: %4$s \n\n Thank you!</string>
- In the
SummaryFragment
class, update oursendOrder()
method to use the new quantity string. It would be easiest to first figure out the quantity from the view model and store that in a variable. Sincequantity
in the view model is of typeLiveData<Int>
, it's possible thatsharedViewModel.quantity.value
is null. If it is null, then use0
as the default value fornumberOfCupcakes
.
Add this as the first line of code in our sendOrder()
method.
val numberOfCupcakes = sharedViewModel.quantity.value ?: 0
The elvis operator (?:) means that if the expression on the left is not null, then use it. Otherwise if the expression on the left is null, then use the expression to the right of the elvis operator (which is 0
in this case).
- Then format the
order_details
string as we did before. Instead of passing innumberOfCupcakes
as the quantity argument directly, create the formatted cupcakes string withresources.getQuantityString(R.plurals.cupcakes, numberOfCupcakes, numberOfCupcakes)
.
The full sendOrder()
method should look like the following:
fun sendOrder() {
val numberOfCupcakes = sharedViewModel.quantity.value ?: 0
val orderSummary = getString(
R.string.order_details,
resources.getQuantityString(R.plurals.cupcakes, numberOfCupcakes, numberOfCupcakes),
sharedViewModel.flavor.value.toString(),
sharedViewModel.date.value.toString(),
sharedViewModel.price.value.toString()
) val intent = Intent(Intent.ACTION_SEND)
.setType("text/plain")
.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.new_cupcake_order))
.putExtra(Intent.EXTRA_TEXT, orderSummary)
if (activity?.packageManager?.resolveActivity(intent, 0) != null) {
startActivity(intent)
}
}
- Run and test our code. Check that the order summary in the email body shows 1 cupcake vs. 6 cupcakes or 12 cupcakes.
That is all for Day81 ✅
Thanks for reading, See you tomorrow!