TOMMY MACWILLIAM: In
this video, we're going
to be writing an Android app
that applies filters to images.
Much like Instagram, we're
going to write our own version,
using a few libraries that we'll
download from the Internet.
So let's get started.
Just like before, let's start
a new Android Studio project.
We're just going to select an
empty activity, because we're
going to write this ourselves.
You can name the application
whatever you like.
I'm going to go with 50 gram.
Again, our package name,
just edu.harvard.cs50.
That's fine wherever
you'd like to save it.
Make sure we've got Java, at least
5.0, and then using AndroidX Artifacts.
So we'll click Finish.
OK, and here is our empty project.
Everything is automatically
generated, just as it was before.
Let's just give this a run to make
sure it looks like what we expect.
OK, we've installed successfully.
And if we open up the emulator,
we've got that blank activity again.
So let's start by writing out
the views for this application.
So we'll have a pretty simple UI.
Let's just display a slot for
the image that you're looking at.
And then we'll just have
a few different buttons.
One button is one to select an image.
And for that, we're going
to look into the images
that the user has saved on their phone.
And then we'll just have
a couple other buttons
that let you apply different
image effects to those images.
So to start writing
our UI, remember we're
going to go into this
Resources folder, into Layout,
and open our Layout XML File.
So just like we did last time, let's
get this text view out of the way.
We don't need that.
Now by default, this is
using a Constraint Layout.
So let's look at a different
container that we might use,
called a Scroll View.
As it sounds, a Scroll View is going
to allow you to scroll contents
that might be too large for the screen.
In fact, that Recycler View
that we looked at before
is very similar, because it's
allowing you to scroll elements
where the actual list is much
larger than the size of the screen.
So let's change this Constraint
Layout, and let's instead
make this a Scroll View.
And notice, that we're auto
completing automatically.
And everything else is
going to stay the same.
Let's just Match Parent,
as we did before.
Now inside of the Scroll View,
let's again use that Linear Layout.
Since we're just going to be
stacking things on top of each other,
we'll have the Image View first and
then a series of buttons below it.
Let's add this Linear Layout.
We want the width to be
match parent, because that's
going to fill the full
width of the screen,
and we want the height
to be Wrap Content.
We want the height of
this Linear Layout to be
equal to the sum of all the
heights of everything inside of it.
So let's create that layout.
And now, let's add a couple
things to it to start.
The first thing we'll
add is an Image View.
As its name suggests, you can use
an Image View to display an image.
So for this, let's specify wrap content
for both the width and the height.
And what this will do is make
sure that the Image View is just
the size of the image that you
loaded, that loads into it.
And let's also give this an
ID, because we know that we're
going to want to access it later.
And let's just call it Image View.
That's everything we need there.
So we can have slash and that tag.
And now below it, let's add a button.
In Android, that's just button.
The width and height, let's
just Wrap Content again,
so the button is just
big enough for the text.
Then from there, let's set
the text of our button.
That's the same property we looked
at before, just Android text.
Let's say this says Choose Photo.
OK, so let's try running
this and see what we get.
We installed successfully.
So let's pull up the emulator.
And right now, there's no image
loaded, so that makes sense.
One last thing to do before we
forget, is to set that orientation.
So by default, it's horizontal.
And so we just want to change that to
vertical, so that all of the elements
stack on top of each other.
So now that our views are setup, let's
tackle this problem in two halves.
First, let's figure out
how we can let the user
select a photo from their phone and
display that photo in the Image View.
Then, after that's
working, let's figure out
how we can apply those
filters to the image.
So everything is all set in the View.
So let's come back to our activity.
Just like we did before, let's get
a reference to that Image View,
so we can save that.
So we'll say Image View.
And inside of On Create, we're going
to say Image View is Find View by ID.
And we gave that an ID of Image View.
So next, let's write a
method to enable the user
to select a file from their phone.
So let's just call this Public
Void, not returning anything.
And we'll call it Choose Photo.
So the way that you can pull up a file
selector for the files on a user's
phone is also with an intent.
But this time, rather than specifying
a class in our own application to open,
we're going to specify
a system wide intent,
so we can actually open some
other app on the user's phone.
This is really nice, because it
means we don't have to manually write
all of this ourselves.
It'd be a real pain if we had to write
a whole image gallery from scratch right
now.
Instead, we can just
use the image gallery
that's already built into Android.
So let's take a look.
We're going to create an
intent, just like we did before.
But this time, in the
Constructor, we're going
to specify intent.action Open Document.
And you can see here, scrolling
through the autocomplete, all
of the different things that
you might want to specify.
But Open Document, as
the name suggests, is
going to open up this gallery
view, where you can select a file.
Next, we want to specify to this intent
what type of file we're looking for.
So we're going to say intent.set
type, And this takes a string.
And we're looking for any type of image.
So the way to specify
that is by saying image/*.
So this says, as long as a file type is
an image, it could be a JPEG, or a PNG,
or a GIF.
No matter what it is, we want to
open it so long as it's an image.
So lastly, we just have
to start up that activity.
So just like we did before, we
can say Start Activity for Result.
We want to give it some intent.
Then we're also going to specify
as the second argument what's
called a Request Code.
And we'll just say 1.
And this is basically a way to
identify where the request came from.
So what's going to happen, remember,
is as soon as this line executes,
we're going to get bounced
out into some other activity.
And eventually, that
activity is going to finish.
And we're going to come
back to this activity.
And it could be the case
that this activity starts off
a few different intents, maybe one to
a file selector, another to a camera,
maybe another to a web browser.
And when we come back
to our application,
we want to know which intent
we just came back from.
So in this case, we'll only have one.
But this Request Code is
basically a unique ID that
tells us where we're coming back from.
So when we come back from this
intent, we're going to say,
let's make sure the Request
Code is 1, because that's
the code we specified for
this getting a file intent.
OK, so the last thing to do here is
to hook this method up to something.
You can see we're getting that
same gray underline we got before,
because it's not used.
So to do that, let's jump
back to our XML here.
And inside of our button,
let's say Android, on click,
and then we just need to
type the name of this method.
You don't need to specify
the class or anything
like that, because Android
already knows what activity
is associated with this layout.
So you can see here that after
I've specified this on click,
we're getting this helpful
error message saying,
the corresponding method handler,
Choose Photo View, is not found.
That's because in order to do this, we
just have to specify a parameter here.
So we can just say View The--
let's make sure we
automatically import it.
And so we're not actually going to use
this parameter, but if we wanted to,
this could tell us which
button was pressed.
So in theory, we could hook
up the same event handler
to a bunch of different buttons, but
we don't need to do that right now.
We can just specify.
We can just add this
parameter, and then not use it.
One last thing is, that
button looked a little small,
because I set the width to Wrap Content.
So let's actually set
this with to Match Parent,
and now the button is going to
be the full width of the screen.
So let's try running this
app and see what happens.
We've installed successfully.
So let's open up the emulator and tap
our now larger Choose Photo button.
Nice, so looks like we've
opened up this new activity.
And it's blue and has
all these nice controls.
And we didn't write this.
This is just something that's
already built into Android,
and we've just popped
out to their activity.
So if I select a photo, nothing
happens, which makes sense.
We haven't specified what's going to
happen when a user selects a photo.
But really, with just
those three lines of code,
we can open up this brand new activity
with all this functionality already
built in for us.
So now, let's take a look at how
we'll handle that data that's
coming from the activity.
To do that, let's open up
our main activity again.
And we're going to override a method
called On Activity Result. Again,
we're just letting autocomplete
do all of that for us.
But this is a method that's defined
in that App Compat Activity, that's
going to be called by the system when
I exit out of that activity I opened.
So once I select a photo, I'm going
to jump into this method here.
Now, you'll notice the first parameter
to this method is that Request Code.
So when I come back from that photo
picker, the value of this variable
is going to be 1.
Next, we have this Result Code.
And this basically is a
way for the other activity
to specify whether or not
there was some kind of error.
Lastly, we're getting back an
intent, which as you'd guess,
is a way for that activity to pass
back any data it wants back to me.
So the first thing you
want to do is call Super.
Could be the case that this on activity
results in App Compat Activity,
doing something important.
So let's just call Super to make sure.
Now, we want to check
a couple of things.
First, we want to make sure
that Result Code is OK.
So this is a special constant that's
defined in the activity class.
It just indicates that
nothing went wrong.
We just want to check for that.
And we also want to make sure
that we got some data back.
So we'll say and data
is not equal to null.
If we didn't get a data back
or something went wrong,
then we don't want to
try to load the image.
So now, we want to extract
the image from this intent.
And doing so is a little
bit verbose, there's
a few different things
we're going to have to do,
but let's just walk through them.
The first thing you're going to get
from this intent is a URI object.
Now URI is just like a URO, just sort
of specifies the location of something.
And to get that, I'm
going to say data.getdata.
And so in this intent, there's some
data that's being passed along.
And this is going to give
me a path to that data.
So now that I have a URI, I
want to open up that data,
since that URI is pointing
to some image on disk.
To do that, I'm going to call another
method that's defined on Activity,
called get content resolver.
This is just a special
class that's going to allow
you to do different file operations.
So there's a method on that class
called Open File Descriptor.
And it looks like from my autocomplete,
this takes a couple arguments.
The first one is sum URI to open.
And the second one is
the mode to open it in.
You might remember this from earlier
in the course, but if we specify R,
that's saying we just want to read this.
So my autocomplete tells
me that this is going
to return some object, called
a Parcel File Descriptor.
So let's just save that here.
So from that Parcel File
Descriptor, this is basically
a wrapper around that content.
And looks like I've got a
red under headline here.
So let's see what it says--
unhandled exception.
So that means that somewhere in
here this exception, file not found,
could be thrown.
That makes sense, because if I
try to open up an invalid URI,
something could go wrong there.
So let's surround this block of code,
as we did last time, in a try catch.
So let's say try, bump
the indentation here.
And then we're going to catch
this file not found exception.
If we do have that, let's
just use that log class again.
We'll log, tag of CS50.
We'll say image not found.
Then again, we'll pass
along that exception
object so that we can print it out.
Finally, we want to change this Parcel
File Descriptor into a different type
of object, called a File Descriptor.
Luckily, that's pretty easy.
We can just say File Descriptor.
File Descriptor is Parcel File
Descriptor, get File Descriptor.
So this is kind of some of the
verbosity I was talking about.
But all you're really doing is just
going through a few different objects
to eventually get the
data that you want to get.
So now that I have a reference
to this File Descriptor,
I want to load it into an image object.
To do that, I'm going
to use the class bitmap.
This is a class that's
also defined in Android.
And it's basically used to load images.
So for now, let's just
call this Bitmap Image.
So now, to go from a File Descriptor
to an image, I can say image equals,
I'll use this class
called Bitmap Factory.
And this is just a class that
has a bunch of static methods.
And when you see the word
factory, that basically
means it's a class whose job
it is to instantiate objects.
So inside a Bitmap
Factory are some methods
that are used to create bitmap objects.
One such method is one
from a File Descriptor.
So I have Decode File Descriptor here.
And that's where I can
pass in my File Descriptor.
So another convention would be to
say New Bitmap and pass something in.
Another way to do that is
just to have some factory.
And the factory is producing
instances of bitmap.
So this Decode File Descriptor
method, it returns a bitmap object.
All right, so just a
couple more things to do.
Let's close out this File Descriptor,
since we don't need this file anymore.
We've already opened it.
We've decoded it.
We've loaded it into memory.
No reason to leave
the file open anymore.
And now it looks like we've
got another exception.
So this is Java IO exception.
So let's just change this file
not found to an IO exception.
And it just so happens that that's going
to cover all of the exceptions that
might be thrown here.
And so now, the last thing we need
to do is just load up our image.
So from the Image View, we have
this method, set image bitmap.
That's going to take a bitmap
object, which is great,
because that's exactly what we have.
And now we can save this.
So just to recap what we just did there.
So we got back an intent
from this other activity.
And this intent sent along
some data in the form of a URI.
We then took that URI, which is
just a path to something on disk.
We opened it up.
We loaded it into an image.
And we took that image and
loaded it into the Image View.
So the Image View is
ultimately what's going
to display that data on the screen.
So let's try giving this a run.
Everything looks like it
installed successfully.
So let's jump to the emulator.
Let's tap on Choose Photo.
Select this photo that I already
have downloaded onto my simulator.
And there we go, we've displayed it.
And you'll notice that there's no
sort of weird stretching or cropping
happening with the photo, and that's
because we've specified that Wrap
Content, rather than giving it
an explicit height or width.
So now that we have this photo loaded,
let's apply some image filters to it.
So Android doesn't have
a lot of this built in.
So we're going to turn to
some third party libraries
to use some image filters.
So let's take a look at the
libraries that we'll use.
This first library is an image library.
And I'm on the GitHub page here.
And it says Android transformation
library providing a variety of image
transforms.
Great, so let's scroll down a bit.
And they have this handy
dandy GIF that shows
you all of the different transforms
that they can apply to an image.
So we've got some cool stuff here.
Looks like they've got a sepia thing,
the sketch one looks pretty cool.
So this library looks pretty good.
I want to use this one.
So then we have this
section, how do I use it?
And this is a snippet
from a Gradle file.
So it looks here like we have
another implementation line.
We've seen this before.
But at the end of it, it says 4.x.x.
We're actually meant to replace that
with the actual version of the library
we want to use.
So let's do two things.
First, let's just copy this.
And we'll add this to our Gradle file.
Just like we did before, paste it in.
But we don't want to leave
those two X's, so let's go back
to this GitHub page.
And at the top you can see
the latest version is 4.1.0,
so we're just going to type that in--
4.1.0.
So let's come back to GitHub.
And it also says if you want
to, use the GPU filters.
I don't know what that is, so
let's just keep scrolling--
transformations.
OK, so here's this GPU filter thing.
OK, there's that sepia,
there's that sketch.
So it turns out I do want
to use this other thing.
So they link to this other repo here.
And that's this library that
this other library depends on.
So in order to use those
transforms, we just
have to load in this other library.
Same thing, this gives its own
little implementation line.
Let's just copy that, paste
that into our Gradle file.
And then, just like
before, let's just replace
those X's with the current version.
Looks like it's 2.0.4.
So 204.
And now we've added those
dependencies to our project.
Let's click Sync to
actually download them.
Looks good.
The sync was successful.
So now we can start
using these libraries.
So the first thing you want to do is
add a few more buttons to our view.
So let's go ahead and do that.
Let's just start with one.
Let's copy this button.
And we'll change the text to--
let's start with that sepia filter.
I really like that one.
And then on click, let's call
a method called Applied Sepia.
We're going to get a red
underline immediately,
because we haven't
defined that method yet.
So let's go ahead and do that--
Public Void apply sepia.
Again, let's just give
it that view parameter.
We're probably not going to use
it, but it needs to be there.
And let's just give our project a
make to make sure there's no errors.
And there's not.
So looks like we've
compiled successfully.
OK, so now we're ready to
start writing our filters.
So I just found this library.
So I'm not really sure
how to use it yet.
So let's check out its documentation.
So here we are on the GitHub page again.
We can scroll past the demo.
And here's a couple
examples of what to do.
So looks like this is using this
other library, called Glide.
And I haven't loaded that in yet.
So let's just make sure
if I need it or not.
So if I click on this
link here, yep, so this
looks like it's just another library,
just a sort of generic image library,
that this other library also depends on.
So simple, let's just copy, paste
this into our Gradle file, sync,
and we're good to go.
So now I have that last
dependency that I need.
So now I can start
following this example.
So looks like I can say Glide.
Here's some static class that's
provided in that Glide library.
It's going to say with this, so this is
probably a reference to some activity.
Then it looks like there's a load
method, where I can load in some image.
And then I have this
method called Apply.
So OK, that looks neat, looks like you
can apply some transformations here.
That's great, that's what I want to do.
Then finally, there's this method, into,
that looks like it takes an Image View.
And so what's that's going to do is draw
out the result of those transformations
into an Image View.
So that's great.
This looks like it's really easy.
It's just one line of code to a plot,
to load an image, apply filters to it,
and load into my Image View.
So that's great.
That's exactly what I want to do.
So let's give this a shot
with that sepia filter.
OK, so let's open up this main
activity and follow those instructions.
Let's say glide.withthis.
That's my activity.
Then what was next?
Next was load.
Looks like there's a load
method here that takes a bitmap.
I called my bitmap image.
All right, after I loaded it,
I wanted to apply some filter.
Now let's just jump
back to documentation
to see what the argument is.
So, OK, looks like we want
to say Request Options.
Request Options, then there's
that bitmap transform.
I've got a bitmap.
So that looks right.
And then I want to
specify a transformation.
So let's go back and
see if I can remember--
OK, so it's this sepia
filter transformation.
Let's create a new one of those.
And let's see-- OK, I
haven't imported this yet.
So I can just have Android Studio
automatically import that for me.
Now, let's just format this a little
nicely, so I'll have one per line.
So that's going to apply
the transformation.
And last, they're going to specify
an Image View to load it into.
And there we go.
That's that same one line
that we just looked at.
But this time, we're applying
the filter that we want.
And you notice here, Android
Studio is being a little helpful.
Again, it's telling you at each stage of
this chain what each step is returning.
So this first step looks like it
returns a request manager, and so on.
So now, let's try giving this a shot.
Let's run our app.
Looks like we're installed.
There's our new sepia button.
Let's click Choose Photo.
Select that photo and then add sepia.
And there we go.
We've applied an effect to that image.
So now let's add a few other filters
to see what else this library can do.
A couple that caught my eye on this list
was one was Tune and one was Sketch.
So let's just add both of those.
Just like I did before, let's
add a couple buttons first.
Let's go one here and one here.
So let's say this one is Tune.
We'll call this Apply Tune.
And this one is Sketch.
We'll call this Apply Sketch.
OK, my layout is good to go.
Let's jump back to the activity.
Let's define those two methods.
Public Void, Apply Tune.
And Public Void Apply Sketch.
OK, looks good.
And so now, naively, what I could do
is just kind of copy, paste this code.
Say I was going to copy it here.
Let's just switch this to
tune filter transformation.
But there's kind of a lot
of repeated code there.
And so let's see if we can factor some
of this out into a separate method.
So let's look at these two
method implementations.
And it's mostly the same.
So let's see what's different.
It looks like the only difference
is this transformation object.
So maybe let's create a new
method that's just called Apply.
And let's pass in that
transformation object.
The problem is, is that
we need to specify a type.
And it doesn't look
like, from just reading
this code, that they're the same type.
They're these two different classes.
But maybe there's some common base
class or interface that they both share,
and then we can use that
as our type instead.
So as a shortcut to figure
out what that might be,
I'm going to use my autocomplete.
So I'm going to go
back to Request Options
and just take a look
at this method again.
And you'll notice that as
soon as I do that, it's
going to tell me the type
that's passed into this method.
And it looks like that type is
a transformation of type bitmap.
So that's the type that I
can use in my new method.
Both of those transformations implement
this interface or extend this class.
It doesn't matter.
All that matters is that
it's a type that I can use.
So that means that we want our type
to be a transformation of a bitmap.
And we'll just call it Filter--
awesome.
So now, let's move this code into Apply.
Now rather than using this
sepia filter transformation,
we're going to use the
filter that we passed in.
OK, so now let's use this method.
Let's say Apply.
And then we're going to pass in a
new sepia filter transformation.
And similarly, here, we can remove this,
and Apply a Tune Filter transformation.
So now let's run a make on this project.
So it looks like we have
some compile errors here.
Let's take a look at what it is.
So this says type android.view.animation
does not take parameters.
And so that looks a little suspicious,
because that's not actually
the transformation that
we wanted to import.
So Android Studio actually
auto imported the wrong thing.
So let's just jump
back up to our imports,
expand this, let's look
for that transformation.
And we can just get rid of that.
Notice we also have
another unused import.
So let's get rid of that.
What we actually want to import
is that other transformation.
So we're looking to import, when we
look at using autocomplete again,
this was the name of that library.
So let's use that instead.
So now, we can just
get rid of this again.
Now, if we scroll back
up to that import list,
you can see now we're importing
the right transformation.
And so this is where packages
are really important,
because you might have some class
name, like transformation, that's
used in a few different packages.
We found out that, apparently,
there's some Android
class called transformation.
But it's in a different package.
So we want to make
sure that we're always
importing from the right package
in order to use the right class.
So let's try building again.
There we go, no compile
errors this time.
So let's try it out.
All right, we've installed successfully.
Let's pick our photo.
Make sure sepia still works.
It does.
Now, here's where the Scroll
View is going to be handy.
You'll notice that not all
the buttons fit on the screen.
So we already added that Scroll
View so we can scroll around.
Now let's try out our new filter.
All right, that's pretty interesting.
And so now, let's add
that one last filter,
but we can do so really, really
quickly, because we already
have this helper method.
So all we need to do is say Apply.
Then let's just pass
in that sketch filter.
I don't remember what it was called,
but the autocomplete saved me.
So now, let's run the app one last
time and check out our new filter.
Installed successfully.
Let's pick our photo.
Hit the new Sketch button.
And there's our filter.
So that's it, we've built
our simple image filter app.
And notice how simple that was.
We're really able to leverage
those third party libraries
and use code that other people
wrote, bring that into our app,
and then build something really
cool with only a few lines of code.
