seattle-java-401d1

CF Accessing Camera and Uploading Images

Reading

Resources

Code Review

Topics

If an image is small it might show up super tiny. If an image is huge it might explode and fill up the entire view of an app. It’s good to scale an image to try to fit it so it fills exactly the space you want.

It’s not enough to say “small images should get bigger, and big images should get smaller.” There are a variety of ways to define exactly how an image should be scaled.

Along with the size of the an image (let’s say it’s width) we must also consider the aspect ratio of an image (the ratio of its width compared to its height). Let’s categorize image aspect ratios into three general types:

Here’s an easy way to keep these aspect ratios in mind. Remember these three objects:

Whenever you’re making an app consider how pictures of these objects (these aspect ratios) should be displayed in your app.

Android defines eight different values for the scaleType attribute. Let’s see what they each do.

Here’s a repo bundling together lots of resources for you to understand scale types. The repo has an app with one ImageView for each different scale type. There’s a button in the app that cycles through different images (small, large, square, tall and wide) so you can see how different types of images are displayed in an app for each scaleType.

https://github.com/codefellows/android-imageview-scaletypes-demo

Also, refer to this website or the Android docs for quick access to this info:

Checkout the app, review it’s code, run it and cycle through the images to observe together how the images different images behave with different scaleTypes.

Configure the ImageView Properly

Review the basic structure of an <ImageView> while looking through the app. Describe the rationale for the layout_width and layout_height values.

In this Instagram-like app we want images to stretch to fill the entire width of the screen. I’ve personally found centerCrop to be the best option.

Notice that the images scaled with centerInside, fitCenter, fitEnd and fitStart have lots of extra padding above or below the images. There’s another property adjustViewBounds you can add to remove this extraneous padding.

Add the adjustViewBounds and scaleType attributes to the <ImageView> element to configure it to display correctly.

<ImageView
  android:id="@+id/imageResult"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:adjustViewBounds="true"
  android:scaleType="centerCrop"/>

Intro to Intents

Dev Guide: https://developer.android.com/guide/components/intents-filters.html

The Android operating system uses “Intents” to send messages between applications. This is a very Android concept. Apple’s iPhones and their iOS don’t have a real real equivalent to Android’s Intents.

source

Intents are what Android calls the messaging system under the hood. Android users don’t need to know that Intents exist. Here’s what Intents looks like from a user perspective:

I can open Facebook, go to an event page and see an Address. If I click on the address Android allows me to interact with it in a few different ways:

One of the coolest things about Android is how it allows users to customize pretty much everything.

If you have multiple browsers installed on your phone and click on a link like http://www.yahoo.com Android will prompt you and ask which browser you’d like to open the link with.

iOS does not have Intents. It’s frustrating. When people click on an address in a Facebook app on iOS iOS pretty much forces users to open the address with the Apple Maps app. It’s possible to install Google Maps on iOS, but it’s not possible (correct me if I’m wrong, I checked) to make Google Maps or any other map app the system’s default map app.

To be fair, the Apple Maps app does include “Share” functionality that allows users to open another Map app. Still, you must invoke this manually and iOS won’t route things to different apps automatically.

In summary, Android has something under the hood called “Intents.” Intents are part of Android’s inter-app messaging system. Intents allow users to take a piece of information (like an address, or a URL) and choose an application they want to interact with that data.

Intents allow your phone to do several awesome things:

Intent Specifics

Here’s what an Intent looks like inside a program. We create a new Intent() object and use the built-in MediaStore.ACTION_IMAGE_CAPTURE to tell it we’re sending off an Intent to capture an image. This Intent will launch the camera so we can take a picture.

The if statement is there to see if there’s any activity that will acknowledge it knows how to handle this type of Intent. If there’s no application that can handle that type of intent then our application won’t even bother sending it off.

// This int is defined by our own app. It's like a cattle brand. We use it to
// identify different types of Intents when they finish and come back to our app.
int REQUEST_IMAGE_CAPTURE = 1;

Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
    startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);
}

Let’s write an application that generates this intent and see if we can get it to launch the camera and take a picture.

Using the Camera

In order for our app to use the camera we’ll need to modify the application manifest to add a special permission specifically stating it will use the camera.

<manifest ... >
    <uses-feature android:name="android.hardware.camera"
                  android:required="true" />
    ...
</manifest>

Add the Intent code from above and let’s see what happens.

The application should launch, and immediately launch the camera to take a picture. And then that’s it. We need to add an ImageView to actually see it.

Add an ImageView

The application needs somewhere to display the image. Use the <ImageView> view. We’re trying to configure the <ImageView> here to fill the whole width of the screen so it stretches across our entire app nicely.

We’ll also need code to populate the <ImageView> with image data once we receive it from the camera.

<ImageView
  android:id="@+id/imageResult"
  android:layout_width="match_parent"
  android:layout_height="wrap_content" />

Add a private field at the top of our application to save a reference to the new <ImageView>.

private ImageView mImageResult;

The image data is sent to our application via an Intent through the apps onActivityResult method. We need to override this method, receive the Intent, extract the bitmap data from the intent and insert that image data into the <ImageView>.

Notice that we check the requestCode against the int value we made up to identify what the original intent was, REQUEST_IMAGE_CAPTURE. We do this because this one method deals with all intent results that come back to our application. When we send out more than one type of Intent we need some way to disambiguate them when they come back.

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {
        Bundle extras = data.getExtras();
        Bitmap imageBitmap = (Bitmap) extras.get("data");
        mImageResult.setImageBitmap(imageBitmap);
    }
}

Run the app, take a picture, and see what happens!

Spoiler alert: the image shows up very short. It doesn’t stretch to fill the entire screen. What’s going on?!

Common Intents

Cool. We can take pictures and display them in our app. What else can we do? Review this Dev Guide showing common Intents in Android.

Some Intent labels are stored in different namespaces, like MediaStore., AlarmClock., or simply Intent.. Actions under Intent disambiguate themselves by using different URI patterns. URIs look a lot like URLs. If you’re really interested in the technical differences of them, then here you go:

Here’s a page describing URIs vs URLs: https://danielmiessler.com/study/url-uri/

Here’s a wikipedia quote:

One can classify URIs as locators (URLs), or as names (URNs), or as both. A
Uniform Resource Name (URN) functions like a person’s name, while a Uniform
Resource Locator (URL) resembles that person’s street address. In other words:
the URN defines an item’s identity, while the URL provides a method for finding
it.

Here’s some different common Intents. Notice that Maps and the Browser both use the same Intent.ACTION_VIEW to start themselves off. These Intents are disambiguated based on the string passed to Uri.parse("foo").

The Maps and Browser Intents gather their information from data inside the URI string. The Alarm Clock, message and text messages intents get their data by having the Intent filled with Extra information.

Alarm Clock:

Intent intent = new Intent(AlarmClock.ACTION_SET_ALARM)
  .putExtra(AlarmClock.EXTRA_MESSAGE, message)
  .putExtra(AlarmClock.EXTRA_HOUR, hour)
  .putExtra(AlarmClock.EXTRA_MINUTES, minutes);

General messaging apps:

Intent intent = new Intent(Intent.ACTION_SENDTO);
intent.setType(HTTP.PLAIN_TEXT_TYPE);
intent.putExtra("sms_body", message);
intent.putExtra(Intent.EXTRA_STREAM, attachment);

Text messages only:

Intent intent = new Intent(Intent.ACTION_SEND);
intent.setData(Uri.parse("smsto:"));  // This ensures only SMS apps respond
intent.putExtra("sms_body", message);

Dev Guide: https://developer.android.com/guide/components/intents-common.html

Trouble Points

Uri photoURI = FileProvider.getUriForFile(this,
  "com.example.android.fileprovider",
  photoFile);