[MUSIC PLAYING] ANDREW BROGDON: Hey, everybody. I'm Andrew from
the Flutter team, and welcome to our workshop
on adding a webview to your Flutter app. You know, Flutter widgets
are a fantastic way to display content,
but sometimes you just want to show
your privacy policy or maybe you have a registration
flow on your website that you haven't
implemented in mobile yet. Webviews can be really handy
for that kind of thing, and the webview_Flutter
package is a great way to handle getting a
webview into your app. In this workshop,
we'll be taking a mobile app that runs
for iOS and Android and adding a webview to it. Fun fact, by the way. I became a manager
about two years ago, and since then I've been
slowly forgetting how to code, so I may make some mistakes
as I go through this. You probably won't
see them, though, because I work with a
fantastic production team that's literally paid
to make me look smart. So if you make some mistakes,
don't worry about it. You probably just don't have a
production team, so keep at it, get yourself a great job,
and hire one for yourself. All right, let's get to it. So this is adding webview
to your Flutter app. You can find a link to this in
the description for this video, by the way, if you're
watching on a desktop. And it's all about adding
a webview to a Flutter app and then manipulating it using
the API that the plugin makes available. And of course, we'll be
using the Flutter website as we do that. That'll be the website
that we pull up in this as we go through this. So setting up your
Flutter environment, I've already got myself
sort of squared away here. I have IntelliJ IDEA. That's my preferred IDE. I've got the Flutter
SDK installed, and I'm going to be
doing this on Android. Works just as well,
by the way, for iOS. I just happen to have
an emulator already up and running, so
I'm going to use that. And I'll have that and
my editor side by side, and I'll walk through
the codelab step by step and we'll see how far we get. All right. So getting started here. Looks like we're going to create
a project on the command line, so let's start with that. Get on here. I've got a clean folder. Right in here I'm going
to use the Flutter SDK to create a new project. And so it's Flutter pub get. There we go. And then I'm going
to run IntelliJ. As I said, that's my
IDE of choice here. And let's get this open. Awesome. OK, let me go ahead immediately
and make this a little bit bigger so everybody can see. I know some of y'all
are watching on phones. I want to make sure you
can see the code here. And let's go from there. So we made that. We're going to add the webview
Flutter plugin as a dependency, and we can do that also
right from the command line. I could do it by editing
the pub spec directly, but for simple stuff like
this, just adding a plugin-- oh, in the wrong one. It does help to be in
the right directory. There we go. For simple stuff
like this, I can just add it using Flutter pub
right on the command line. It's running pub get. Awesome. All right. So created an app, added
the webview Flutter plugin. Now I need to configure
my Android minimum SDK. It's something that
comes up occasionally. With plugins they include both
Dart code and Native code. Of course, sometimes
that Native code requires a certain
minimum SDK level. In this case it looks
like the min SDK is 20, so I just need to hop into
my Android sub project. Here, let me pull this up. Android app. I'm going to go ahead and get
into the build.gradle file for the app. Let me do my zooming
trick for this as well. There we go. I'm just going to scroll down
here, and so there we go. So we've got minimum
SDK is on line 48, and it's just using
the default minimum SDK version from the SDK itself. I'm going to update that
to 20 because that is what is recommended by the codelab. And let's run it just to-- let's
just make sure everything runs. Always a good idea. So I've got my
emulator open here and let's run this bad boy. There's our app. It's coming up. Flutter's reminding us that
it supports hot reload, and there we go. The counter app, as we've
all seen from Flutter create. So this is-- we haven't done
any Dart code changes yet, but we have included
that webview plugin and we have changed the
minimum SDK for Android, so we're ready. Let's go on to step
four, adding webview-- adding the webview widget
to your Flutter app. Replace the content
of lib/main.dart. So we're basically going
to erase everything in that main file and put
it a bunch of new stuff. Piece of cake. I wish I could cut
and paste all my code. So we've gotten rid of
the previous contents. Then we have web app that
just looks like a Scaffold. You see that on line 22. We have an app bar
and then there's that webview widget on
line 26 here, right there, and we're giving it an
initial URL of flutter.dev. So yeah, let's do-- let's
save this, and that's going to freak out because
I need to do a hot restart, so let me just go run that. We'll do a hot
restart because when you replace your entire app,
hot reload can occasionally freak out on you. Don't worry about it. You just do a hot
restart and there you go. Awesome. So that's how easy
it is, apparently, to get a webview into
your Flutter app. You just add that dependency
and pop in the widget. There we go. There's the Flutter website. I can scroll around in here. I should be able to
click and go to places. Yep, there it is. The emulator is giving
me a little trouble with the icon font. Don't worry about that. It does work on real phones. Otherwise one of
our tech writers would be freaking
out right about now. But the icons are
there on a real phone. Cool. So that's step
four, adding, again, webview widget, a webview
widget to your Flutter app. Want a little note in here about
enabling hybrid composition. There are a couple
different modes. Webview is, of course,
a platform widget. It's actually running a
native widget-- a native view component, and
then finding a way to incorporate that UI into
the Flutter widget hierarchy. There are a number of
different ways to do that, and hybrid composition
is one way to do it. It is not something you need to
mess with though unless you're running into issues. It's more efficient to use-- to allow webview to
pick its composition mode by default. And so
unless you have a good reason to do it, I would just
leave that bit alone. So we're going to skip past it. That is optional in the codelab. And then there we go. So we've got the
app running, we've got a webview widget
in it, and it's loading the Flutter website. All right, let's
keep going here. Listening for page load events. So sometimes we might want
to know how long it's taking for a page to load, right? Maybe you're not on the
greatest network connection in the world,
something like that. You might want to be able
to put a little progress indicator up there. And so we've got
some stuff for that. So this is webview stack,
so it looked like we're going to make a new file. Let's go back into our editor
here, open up the project side. I'll close that file. We no longer need it. And close Android. We don't need that anymore. Lib-- let's make a
little source directory. We'll make a new file in here. I believe it's called
webview stack is what it wanted us to use. Awesome. So new source file, and fill
it with the following content. I can absolutely do that. Grab all of this here,
paste it in there, and once again, I'll do
my zooming trick here. There we go. All right, so now we have
a webview in a stack. As you can see, we got
a stack widget here, so we've got the webview
and then a linear progress indicator right below it. And so we're going
to incorporate this into that main file
we already have. Let's put that in there. What else do we need? So we just imported
that new file. Ah, OK. We're going to change
our web view here from webview to
webview stack, which I do believe takes a parameter. There we go. Awesome. Can we remove that? I didn't see it load. Let me try this again. Do a hot restart, see if we
can see that linear progress indicator as it goes. OK, so it was there, but that
is really subtle, especially at this zoom level. Let's just mess with
that a little bit. I think there's
a minimum height. Yeah. Let's make it tall. And I bet there's
a-- yep, color. OK. How about let's do yellow. That's also a Flutter color. There we go. And now let's do a little hot
restart, see if we see it now. Ah, there it went. You make things much
taller and yellow and they're easier to spot. OK. Let's look at how
that's being done. So webview has a few methods
here on page started. This is on line 20. There's also on progress
and on page finished. And so those are callbacks
that webview offers. We can set those, give Dart
functions to those properties, and it will call them. And then inside
each of these, it's just call and set state to set
the loading percentage, which is a field right here in the
state object, and that's what-- that is what the loading
progress indicator is using. So it looks like we're using
those three methods to listen and then once loading
percentage hits 100, the linear progress indicator
is no longer displayed. You can see this
collection if right here. All right. Well, cool. So we got that working. We are now listening
properly for pageload events. All right. Now let's work with
the webview controller. So this is a pattern you may
have seen in other places, if you've ever done text
input in Flutter, for example, the text edit controller. It's a pattern that's
used whenever you-- widget's going to
have a state object and maybe you might need to
give that state object a poke or listen to it from somewhere
else in the application. You need some reference that you
can hold on to somewhere else, and that's the same thing
here with webview controller. So this is going to
allow us to control the webview from different
parts of the app's UI. All right, so we're going
to enter async here-- oh, and yeah, we get to
use a completer too. This is awesome. So not something that you
use very often in Flutter. Not something you have
call to use, really. You may be more familiar,
maybe, with a stream controller. A stream controller is an
object you can instantiate and you end up with-- it allows you to create a
stream from scratch, basically. You get both ends of
the pipe, so to speak. You get a sync that
you can put data into and you have the stream end
where the data pops out. A completer is the same thing,
but for a future rather than a stream. So a stream, multiple values can
pass through a stream over time with a future--
with a completer, you just have the one future. It's going to be uncompleted
to start and then you'll complete it once
either with data or value-- or an error, rather. And so we're going to
add dart async here to-- are we still in webview stack? Yup. All right, so we're going to
add the Dart library at the top. Those always go at the top. I'm going to-- webview stack is
now going to take a parameter. And it's yelling at me because
I don't have that field of font. We can do that right now. And so this is where it takes
a completer as a parameter, and you'll see why right here. So the way the webview works is
it is managing a native view, right? This instance of the
webview is a platform view, and so the webview widget is
responsible for managing that. And so you create a
web view, it runs off to create the platform
view that it depends on, and then it comes back when it's
done and says, OK, I'm ready. Here's a controller to use
when controlling this widget. And so you can see right
here, this onWebViewCreated is a callback that gets called
when that webview is created. So we can pop this
into here and you can see it's completing
that completer with a value. So once it gets the
web view controller from the web view
widget in this callback, it completes that future. And so now we can use that
future elsewhere in the app to interact with the
webview, which I'm guessing is what the rest of
this page is all about. So crafting some
navigation control. Yep. OK. So we got another new file here. This is navigationcontrols.dart. Let's make that happen. So new file. Controls.dart. There we go. And I've got a lot of
content to stick in there. So this is just making a row
of buttons, it looks like. Once again, zooming
in for everybody. And so yeah, this is making-- so it's using this future
for the web controller. If the future is uncompleted
or the controller is null, it's just making some
buttons that aren't enabled. They don't have an onPressed. If it is, then it's passing back
a whole bunch of icon buttons, looks like, with handlers,
onPressed handlers, that are using the controller. Actually, these
aren't even buttons. These are just icons. Yeah. All right, so now
we have this file. I'm guessing now we need to
go into main.dart to use it. So we do. OK. That's not cutting and
pasting the right thing. There we go. Let's go back into main. There we go. And put the webview import back. Let;s go with spacing in here. That would go there. And there's this note. I'm going to import
the navigation controls file we just created. I'm working real hard
to keep my imports tidy. It's very important,
alphabetical order. And here is where that
completer is created. There we go. Does that go in
the state object? Yes, it does. Mhm. And then-- OK, so
here on the app bar we're going to
have those buttons. Let's get those in
after the title. And then webview stack now takes
that completer as a parameter. And it's going to yell at me
because it can't be constant anymore because I'm giving
it a nonconstant parameter. And I want to say that's it. Do I have anything
else in Dart analysis? Oh, my widget test hold on. Testing is not actually
part of this codelab, which makes sense that
the widget tests would be losing its mind over the fact
that I've changed all the code. None of the syntax in that
test even applies anymore, so let's get rid of that. Won't have to worry about it. OK. Let's save this real quick. And I got a message from
Dart, reload rejected. Const class cannot
remove fields. Try performing a
hot restart instead. OK. I always do whatever
the SDK tells me. We do a hot restart here. And there we go. You saw those buttons pop in
once the webview had loaded. And so now we should be
able to reload, I believe. Yep, it's reloading. And if I go in
somewhere, I should be able to navigate back. Awesome. And so let's take a look again
at those navigation controls. Let's see. So here is the arrow back,
and it's just calling-- calling some methods on
the controller, canGoBack. It's checking to see
if that's possible. Oh, so actually
if I do this, it's going to pop up a
snack bar and say, hey, there's nothing in the history. I can't go back anymore. Interesting. And then arrow forward is wired
up to controller.canGoForward, and then goForward. And then the last icon button
just goes controller.reload. Awesome. Now we got the web view
controller in place and we have some working
navigation buttons. Not too shabby for, what? 15 minutes work so far. All right. Anything else we needed
to do in this section? I think we're good. All right, keeping
track of navigation with a navigation delegate. So this is a handy thing. Let's say you are
using a privacy policy. You're using a
webview to display a dock like that from
your team's website or something like that. Maybe you have a
site footer that's got all your social media
icons at the bottom, and in theory somebody could
just scroll the webview down and start going to social
media or pulling up YouTube and watching videos
in the webview in your app. That might not be a
problem, but maybe it's something you want
to prevent, right? And so you can use this
navigation delegate to sort of build a little
fence around your webview and make sure it only goes
to the places you want it to. So let's take a look
here, grab this. This is going to go right
into, again, webview stack. It's just part of our
constructor for the webview. And it's going to go all the way
at the bottom here, looks like. Right there. And so now we've
got this in place. This is just a
simple method that's going to get called any time
the webview's about to navigate somewhere, and it allows our
Dart code to say, hold on, I don't want you to do that. All right. Let's go on to the
menu here, and we're going to add a little
bit to the menu to allow us to test
that navigation. So we got another new
file, lib source menu. All right. Go back into here, do menu.dart. Wait. Cancel that. Misspelled. I would like to delete
that menu folder. Thank you. It was not menu.dart,
it was menu/dart. Managed to confuse my IDE. There we go. And so we've got
some code in here. Let me grab this stuff. We're going to define
a menu in here. And once again, zooming in. There we go. So we're defining a menu here. We have one menu option
for navigation delegates. Looks like we have a
future builder here so once that webview controller
is available, it'll start building the menu. And it's got a pop
up menu button here that has a switch
statement for which choice and has an item builder
where it's just kicking out the individual menu items. Awesome. OK. So in this case when that
first menu item is selected, it's just going to
try to load YouTube. So this will allow us to
test our navigation delegate. All right. And then we need to get this
wired into main real quick. So let's go back there. Menu comes before navigation
in alphabetical order, and we're just going to pop
the menu into the app bar right after the navigation control. So we'll have those
three buttons. Boom, boom, boom,
and then there'll be a menu right after it. And so let's see. There's our menu. Awesome. So navigate to YouTube, and
it's hard to see because I can't zoom in on this bit. But there's a snack bar that
just popped up and said no, basically. You're not allowed
to go to YouTube, not going to let you do it. And so let's go back and look at
that real quick in the webview stack. So that is this navigation
delegate making that decision. So it's a simple function. It takes a navigation request
as its only parameter, and this code is parsing
the URL out of that request and then just checking
the host to see if it includes youtube.com. And if it does, it
blocks it by returning prevent,
navigationdecision.prevent as the decision. And otherwise it returns
navigationdecision.navigate, which means OK,
go do your thing. All right. So yeah, we got some guardrails
now on our navigation, and we have a menu
that we can probably add a whole bunch more choices
to over the next few minutes. All right. Let's take a look at JavaScript,
evaluating JavaScript. So by default, JavaScript should
be restricted pretty heavily on a webview for
security reasons. But you can turn off
those protections, which we are about to do. So we're going to
go in here Mhm. Yep. JavaScript mode unrestricted. There you go. We are flying
without a net here. Awesome. OK. So we just enabled
unrestricted JavaScript, which means we can now do some
things like pass JavaScript into the web view, and that's
what we're about to do. So we're going to
go back to the menu now and add a choice to it. Let me save this. Oh, accidentally
doubled up a line. I'm going to go back
into that menu here. We're going to add another
one of these, user agents. So we're adding another
menu choice to that enum. We're going to have another
code snippet that takes action if that one is selected. There we go. And we're going to have
another pop up menu item. Awesome. And so now we have
two menu choices. This second one is
called user agent. We've got it listed
in the menu now, and we've got a handler
for it when it's called. So if I Save, you should be able
to come over here and see, yep, two choices. Show user agent, and
if I select this one, there's my user agent string. This is the string
that every browser defines to identify
itself to the server. And so let's see how
we're getting that. So here in the handler we have
runJavaScriptReturningResult, and it's just grabbing-- it's the world's
simplest JavaScript-- just a navigator.useragent. So it's not even a full on
method or anything like that. It's literally
just an expression and it's just grabbing
the user agent directly out of the JavaScript
engine running in the webview and popping it in
into a snack bar. Awesome. Let me do that again. Oh, wrong choice. Show user agent. There it is. Yeah, Android 11. Chrome 83. It may be time to
update my emulator, get a different one there. I think I'm running Chrome 90
something on my desktop right now. But it works. That's the important thing. All right. So there's some JavaScript
that we were just executing. Anything else in this page? Don't think so. All right, now we have
JavaScript channels. So you may be familiar
with platform channels in the Flutter SDK. The JavaScript channels
are something that come out of the webview package. They're a way for you
to register a channel that the webview can
use when it decides it wants to send you something. So let's take a look at that. We're going to modify
webview stack here. We're going to give it
some JavaScript channels. Awesome. Let's Go back to webview stack here. Now it's going to
yell at me because that createJavaScriptChannels
function does not exist. So let's go make it. There we go. So this is creating
a JavaScript channel. It's giving it a name, SnackBar,
and it has a little callback for onMessageReceived. It just takes the message
and pops it into a snack bar. And that JavaScript
message includes a string as part of its payload. All right, so when we
call this method here, this JavaScript channel, and
we give that to the webview, what it does is create an object
inside its JavaScript context where inside the
web view JavaScript is running in the page, and
that JavaScript Object can be used to send messages out. And I imagine we're about
to get a little bit-- yep, back to the menu. So we're going to
do another one here. There we go. Let's save that. Let's go out to the menu. We're going to do just
like we did before. We're going to add
something to the enum. We're going to add a big
old chunk of JavaScript in a string. There we go. If you haven't seen this, by
the way, triple block quote or triple single quotes
or triple double quotes, either one, are multi-line
strings in Dart. So that's how we can have
this enormous string here on multiple lines, but have it
all be part of the same string literal. All right. And we need another
pop up menu item so that I can select this
new one that we just did. A little hot reload
here, open up the menu. So look up IP address and-- oh, hot restart. I've already created
my webview, I've already got the stuff set up. If I want to give it
new JavaScript channels to begin with, I've
got to do a restart. So let's do that. That's my webview state had
already been set up, right? There we go. Let's try that one more time. Look up IP address,
and there we go. There it is. So let's take a look and see
what's really going on here. So we have run JavaScript. In this JavaScript it's
doing an XML HTTP request, an asynchronous web request,
and it's grabbing my external IP address in JSON format. So it's going out to
an external service, grabbing my IP address. If it's successful,
it's parsing the JSON and then just passing it back. PostMessage here, that's
the JavaScript channel part, SnackBar.postMessage,
and just giving it a string of IP
address and then the IP. There you go. So again, that's an
asynchronous call, right? That's the webview calling
out to some other service, waiting on a response,
getting a response, and then deciding
it wants to send a message into the dark code
that's executing in my Flutter application. And that's what JavaScript
channels allow you to do. They give you a way to give
that webview a chance to-- a mechanism that it can
use to send data whenever it decides it wants to. All right. Cool. Anything else in this section? I don't think so. All right, we're on
to managing cookies. Cool. So we have a lot
to add to the menu. In this section. All right, we got five things. Let's get those into place. Back into menu.dart. I'm guessing I have
five enum values here, and going to convert the menu to
a statefull widget so it can-- ah. So it can keep a reference
to a cookie manager. Fortunately, my IDE can
do that conversion for me from stateless to statefull. Actually, I say that. It's the SDK. It's a quick assess,
technically in the SDK, that the IDE plugs
into and then I can add that field for a
cookie manager right there and get a list of all cookies. All right. So it looks like we're going
to have a bunch of other helper methods here, so let's start
grabbing these and putting them into menu.dart. So there's one. There's two. I wish I could always
add code this quickly when I was working. That's a-- remove a
cookie, I believe, is last. So I've got five helper methods. I need to wire up
five menu options. So we're going to add-- grab those here. These are the handlers. They get executed here. Save those, and we have
some new pop up menu items. Let me grab those. Those will go right
here in the bottom. Going to save them. That's a lot of-- that's a lot of-- OK, good. There's a recommended order of
operations here to use these five menu items in. . And I did change the state
object for the webview, so I'm going to do a hot
restart again just to make sure that all gets done correctly. There we go. All right. In what order shall
we mess with these? So list cookies is first here. Let me do that. Whoop. All right. List cookies. So there's the
cookie, by default, that flutter.dev gives you. OK. Clear cookies. All right. So there were cookies, it
says, now they were gone. Just got that in a snack bar. I'll run clear cookies again. No cookies to clear this time. List cookies, also empty. And then we can add a cookie. So added a custom cookie. We can set a cookie. I'm curious what the difference
is between those two. Let me take a look at
the code in a second. And then we can list
cookies again in there. There's our custom cookie
for foo=bar, and remove. All right. So let's go back and
take a look at the code and see what's going on
in these helper methods. So list cookies is using that
controller to run JavaScript. So it's
runJavaScriptReturningResult, which we used before on
the navigator.useragent to get the user agent. This time it's just doing
document.cookie, so straight up vanilla JS using
the document APIs. Clear cookie is using
the cookie manager. That's that object that we
made that also comes out of the webview
package, I believe. And that's using-- we're its
API to clear out the cookies. And looks like it returns
a Boolean, I think. Yep. To indicate whether there
were any cookies to clear. So here we go. Here's add cookie
and set cookie. So add cookie is using,
again, document.cookie. So this is a vanilla JS. I'm going to use
the normal APIs just as you would with
normal JavaScript, and it's setting up-- creating a cookie that way. Set cookie looks like
it's doing the same thing with cookie manager. So it looks like you have
two options to set a cookie. If you want to you can
use JavaScript directly by calling runJavaScript
on the controller, or you can use that
cookie manager, which has what looks a more structured
way with optional name parameters here to
create the cookie. And remove cookie, a little
bit of trivia I learned checking out this codelab. One way to clear a cookie
is to remake it and give it an expiration date that
has already happened. So here you can see it's
assigning the cookie using document.cookie, but it's giving
it an expiration date of 1970, and I'm pretty sure
it is not before 1970. I would probably have a very
different hairstyle if it was. All right. So those are our five
methods for setting cookies. Awesome. Anything else to
do on this step? Nope, doesn't look like it. All right. We got one left, loading Flutter
assets, HTML strings, and files into the webview. So yeah, certainly
sometimes you might want to use your webview
to access something that's stored externally, like I
said, a privacy policy or terms of service or
something like that. Maybe even open source
licenses, although Flutter does have a way to do that. But maybe you have
some local HTML that you need to
display somehow and you don't want to manually convert
it into widgets, right? Maybe you have a library--
maybe you work in an enterprise and you put one of those
libraries that does HTML based reports that your company has
invested a zillion dollars in and you need to
find a way to use. This is maybe a way you
could bridge that gap, right? Use a webview, get the
HTML from that library, and then pop it in there. Let's take a look at it. So inside pubspec. OK, we're going to use
path provider here. So we're going to
add path provider. Let's go make that
happen real quick. Where's my pubspec? There you are. And again, I'll zoom
in a little here. We're going to go down
to our dependencies and add path
provider right there. And oh, looks like there's
even got a version bump. Awesome. And OK, some assets. So we're going to
load from assets. We actually need to
register those assets. Bunch of comments here in the
default pubspec, by the way. Don't be afraid to take
those out when you're ready. There we go. So we got assets now, we have an
index file, and a styles file-- which do not currently
exist, so I'm guessing I'm about to go make those. Looks like it. Yep. And you know what? I added path
provider, which means we're going to have
to restart this app, so I'm going to go
and stop it right now. We'll have to do a full restart. But right now, we can go in
and make these other files. Assets, too, we'd need a
full restart for, right? Those do not get hot reloaded. So a folder called
assets, folder called www, and a folder called
styles, I believe it was. And then we have an index.html
in that base www folder. There we go. I'm just going to
drop in some content, zoom that one in a little bit. So we got just a basic
HTML file here, title, link to a style sheet-- which
is the other file we're about to make, I think-- and just an H1 header
and a paragraph. And then we have the
world's simplest CSS file here, one directive
for one element. Let me go ahead and grab that. Come here, then we'll
make that in here. Awesome. So again, very
simple stylesheet. Let me go ahead and close
the project explorer. And so we're going to
have some helper methods it looks like in
the menu, again. So we have
onLoadFlutterAssetExample, so let's get that into place. Those are going to go,
again, at the bottom. We'll have load a
local file, so it looks like we've got some
things to work on here. We're going to add a-- go ahead and get
dependencies for pubspec. I'm going to have
a constant up here. So this is just a
straight up string constant with some HTML in it. And I think we need to get these
imports if we don't already have them. So I'm missing a couple here. There's dart.io and
path provider, right? We added that to the pubspec. We should probably
import it somewhere. There we go. And what else do we need? So LoadLocalFileExample
and PrepareLocalFiles. So it looks like this is
going to make a local file on my emulator and then
tell the webview to load that local file into itself. Let's get those in place
and load HTML strings. We did that one, that
one, prepare local file. There we go. OnLoadHTMLStringExample. Cool. So we got our helper methods. Now we just need to add
a few more menu choices. Let's get those going
all the way at the top. Well, almost now. We got that string
constant there. Let's get those in place. I'm guessing we have some more-- yep. [INAUDIBLE] item handlers. Let's get those going. And you're a lowercase
B. There we go. And then we just need
to add them to there. All right. So I think we're ready. Let's fire the int back
up and see what happens. Let Gradle do its thing here. All right, there we go. So we got our webview
firing back up. And of course, we
cleared our cookies, so we get the cookie
policy pop up again. That's awesome. And let's play with some of
these things, these new menu choices. So load a Flutter asset. This is going to load that
file and stylesheets-- there we go-- that
was being used as-- those were the
assets that we added, so those are hardcoded
into the app. And then we have
load HTML string. This should just be
that string constant that's getting put in there. Local demo page. Yep. I'm going to load
local file, which I think is the same string. So let's real quick go
back to the Flutter asset. There we go. Let me do the local file
so we see the color change from the stylesheet. There it is. So you can tell it's
a different thing. It did get the data
in there correctly. And let's check out that code
because I just kind of pasted it in. So we had
onLoadFlutterAssetExample. This is just using an API
method on the controller, loadFlutterAsset, and it just
hands it a Flutter asset path. And that's really interesting
that it don't-- you don't have to give it the path for
the CSS file, I guess. It just knows how to find it. Interesting. OnLoadLocalFileExample,
that's the one-- looks like it's making a
local file, PrepareLocalFile, and it's using that
string constant that we defined all
the way at the top, that a little bit of HTML,
and so it's just dumping that into a temporary file and
then telling the webview controller, load
file, and giving it the path to that temporary file. Even simpler is the load
HTML string example here. It's just saying,
hey, controller. Here's a string. Please display this string. It doesn't get much
simpler than that. It's like the webview
basically saying, tell me what you want me to say and I
will say it, and there it is. So we got those all
wired up and working. Awesome. That is the end, I think,
of step 12, which means that's the end of the codelab. We just covered getting
the webview Flutter package into the app. We got that into the pubspec. We added the webview
widget, and now we've taken a grand tour of the API
and everything that it can do. OK, so that's it for
webview and this workshop. To be honest, I had no
idea the webview widget could do so much
stuff until I started working with this codelab. Speaking of
codelabs, by the way, if you'd like to do
some more of them, head to docs.flutter.dev or
dart.dev/docs to find a bunch of additional codelabs
on both Dart and Flutter. And of course, if you
enjoyed this video, we have new stuff on this
channel about every week or so, so consider hitting
that Subscribe button, and we'll see you next time. [MUSIC PLAYING]