[MUSIC PLAYING] MATT: Hi, everyone. And welcome to the-- welcome to "The Boring Show." it's me and a very
special guest. Would you like to
introduce yourself? DASH: I'm Dash, obviously. MATT: Obviously. And-- DASH: Everybody loves Dash. MATT: Everybody does love Dash. I'm really going to love
Dash for the next hour. I can guarantee you that. So today on "The Boring
Show" we thought, didn't we? DASH: We did. MATT: Good. One of us had a thought. And that thought was that we
would start playing around with a brand new app. We've been playing around with-- DASH: The Hacker News app. MATT: There we go. Thank you for the prompt. The Hacker News app and we've
done a lot of cool things with it. But we thought we'd start
with something fresh today. And one thing we haven't really
checked out is multimedia. DASH: Oh! MATT: Do you know
what multimedia is? DASH: I like to sing. MATT: We will spare you that. So what we thought
we would do is we'd play around with some
audio and ultimately some video. So we're going to start by
creating a new app today, which will be a podcast playing app. Isn't that exciting? DASH: Oh, Dash Cast! MATT: And we're going to
call it Dash Cast for-- DASH: Whoo hoo! MATT: --reasons. So where are we going to start? What are we going to do? DASH: Did you flutter
create already? MATT: I did. I flutter created. If we check out my screen,
we've got a very basic app, which is pretty boring. DASH: Well, that is on theme. MATT: Yes, it is on theme. And that's it. I did flutter create. I added a dash of
Kotlin and Swift, just in case we're playing
around with the lower levels later on. But we are going to dive in. And we're going to
probably for the next hour, we're going to
try to create just the rough shell for this app. And we'll try to get some
audio playback in there. So that will get us started. So we can play. How does that sound? DASH: Sounds great. MATT: Yeah. DASH: I can't wait. MATT: Who's going
to do the typing? DASH: Well, I kind
of hunt and peck. So you might be a
little bit faster. MATT: And I will be
doing the typing. Good. So what are we going to do? We have a basic app here. So let's play around with
adding in some audio. DASH: Sounds good. MATT: Yeah, I'll stop looking
at the person over there. I'll just look at you. DASH: Yeah, I'm right here Matt. MATT: Yes, you are. You're not going to let
me forget that, are you? No. OK, so we are going to dive in. Now, the first thing is, how are
we going to do audio playback? Because I think there's
a bunch of packages we can use for that. DASH: Yeah, thanks
to Pub there's like so many audio
plugins that you can use. MATT: Because you can
count to what, three? DASH: Yes, one,
two, three, many. MATT: One, two, three,
many, there we go. So here we are. Well, let's check it out. We'll do a search for audio. And as you can see, there are
a whole bunch of them here. DASH: There's Flutter
Audio and Audio Player. Both allow seeking, which might
be nice for a podcast app. MATT: OK, so is
that Flutter Sound? Or we have Flutter Audio. DASH: Yeah. MATT: Yeah. DASH: Oh, yeah, that
one's a good one. MATT: That one's a good one? OK, so we'll try it. DASH: There are many good ones. We like all of you
guys, community. Thank you. MATT: Yes, and I
mean, what I usually do when I'm looking for
this and I have a choice, I'll flick through it. I'll check out for
example, Flutter Sound. I'll go over and I'll have a
quick look at the GitHub repo. DASH: You can look
at how popular it is, which is a good indication. MATT: So we've got
some good docs here. This sounds nice. I've got Audio Controller. Oh, it's got a recorder too. That's interesting. OK, and we can seek too. DASH: We can make
our own podcast. MATT: Yes, although,
you wouldn't actually get to see you in that. DASH: Oh. MATT: Oh, indeed. All right, so let's go. And we'll go add this in. DASH: But I still provide
skinsulating conversation. MATT: Did you say
skinsulating conversation? DASH: Skintillating. MATT: Scintillating. DASH: Oh. MATT: That might just be
my terrible Irish accent. OK, so let's add
in Flutter Sound. We're going to go back. We're going to go
to our Pub spec. And as you can see, I haven't
tidied this up at all. So let's get rid of some
of these comments, which are super useful. But we don't need
them at the moment. DASH: Because we're advanced
Flutter programmers now. MATT: Yeah, we're
going to see just how advanced we are over the next,
how long do I have to do this? DASH: One hour approximately. MATT: Only 55 minutes
to go, perfect. OK, so we are going
to go in here. And we are going to
add in our package. And it is Flutter-- DASH: Flutter Audio. MATT: Flutter Sound. DASH: Oh, I can read-- MATT: Yeah, maybe
you need glasses. DASH: --sometimes. MATT: I certainly do. Flutter Sound, and
we are going to make that approximately 1.3.5. And we're going to let Visual
Studio Code do its magic. And there we go. OK, so we have it in there. So the next thing we're
going to add to do is we're going to have
to have a file to play. And so by the way, I have
never looked at this library. No idea what I'm doing in here. So we'll-- DASH: Should we
play my theme song? MATT: You have a theme song? DASH: I was hoping you had
written a theme song for me. No? MATT: You would like me
to sing you a theme song. I say now that's not happening. DASH: Oh. MATT: Oh. DASH: Next week. MATT: Next week on
"The Boring Show." DASH: Next two weeks. MATT: Yeah, stop lowering
our subscriptions. OK, so what are we going to do? We are going to-- let's see. How do we do this? Let's take a quick look here. We have-- well, there's
recording stuff. We have starting a player. We have a path. OK, so maybe what
we want to do is we want to get an audio
file to listen to. DASH: Sounds good. MATT: OK? So because I'm so well
prepared, I don't actually have an audio file. DASH: What about birds
tweeting or something? MATT: Birds tweeting. DASH: If I don't have a theme
song, it's the next best thing. MATT: OK, so we're going
to cut away for a second while I go and
download an audio file. And we'll be back
in two seconds. [BEEP] And we're back. We have gone and hunted through
some public domain Creative Commons sites. And we found a piece of music
that we can use by Incompetech. And you'll find a link to
the music in this show notes. DASH: Yeah, Matt didn't listen
to my suggestion though. MATT: What was your
suggestion again? DASH: Birds singing-- MATT: Birds singing, DASH: Or better, my theme song. MATT: Yes. We'll TBD your theme song. We'll work on that. DASH: OK. MATT: OK, so right, as you can
see, we have this added in. I've created a directory
called Assets and under Assets, Personal Space. I told you about personal space. I don't get paid
enough for this. Under Assets we
have "Surf Shimmy." So we need to add that in. Because whenever
you have an asset, you need to add it
into your piece here. And if I remember correctly,
how does Assets look? Is it Asset or Assets? DASH: Assets. MATT: OK. DASH: Pretty sure. It's also in the comments there. MATT: Which I deleted. DASH: Oh. MATT: Because, you know, smart. And this is an asset. Don't worry. We'll get errors
if it comes out. Are you looking that up? DASH: Yes. MATT: Wow, look at
you beak typing. That's amazing. OK, good. So we have that added in. So let's quickly
get this wired up. So I'm going to go back to my-- this is very much Flutter 101
here, but we'll get there. I'm going to create a new-- we've got-- what
do we have here? We have the usual
Material app, which is wrapping a scaffold,
which has got center and which has got some text,
which is not very exciting. So why don't we add a
Play and a Stop button? DASH: Sounds great. MATT: How's that sound? Yeah? I'm so glad you're
here to help me. DASH: I am full
of helpful ideas. MATT: Yes, yes, you are. DASH: And moral support. MATT: And moral support. My morals are feeling so
supported at the moment. Right, what was I doing? DASH: Adding a Stop
and Start button. MATT: Adding a Stop
and a Start button. OK, Stop and a
Start button, we're probably going to want to
track the state of that. So I'm going to use
a stateful widget. How does that sound? DASH: Really? Not just a raised button? MATT: Well, that's a--
oh, you want me to-- but we need to know if it's
playing or not playing. How are we going to track that? DASH: You have a onPressed. MATT: But it plays. And then you want it to stop. So do you think maybe
a stateful widget? DASH: Oh, you want a
disable/enable thing. MATT: I want a button, which
when I want to play the music, I hit play. And when I want to stop
the music I hit stop. Maybe we need to track it. OK, let's go with one
button and see what happens. Why am I arguing with the bird? Because-- DASH: Indeed. MATT: And I spent ages
thinking this one up. The bird is the word. Yeah, there we go. I knew that joke
would go down well. We're going to call
this Playback Button. Isn't that great? DASH: Wait, so you
want one button? Or do you want two? Just-- MATT: Wait, you just told
me to put in one button. DASH: Yeah, that's
what I'm envisioning. But I want to understand
what you were thinking. MATT: You are such a bird brain. How many buttons do you want? I will go with
whatever you want. How many? DASH: I think one works. MATT: OK, we'll go with one. Don't get upset. We talked about this. DASH: OK. MATT: Yeah, OK. Right, OK, one button. We got a play it back button. We are going to in
here have a-- do you want a raised or a flat button? DASH: Raised. MATT: OK, we're going to
go with a raised button. And a raised button
takes a couple of things. It takes child. And inside the child, we
will have some text, which-- oh, why don't we have an icon? DASH: Yeah. MATT: OK, why don't
we do that instead? Why don't we put in an icon? We could just use
an icon button. DASH: Good point. MATT: OK, we are now
going to an icon button. And inside our icon
button, that is not going to take a child anymore. It's going to take an icon. And we can give it an icon,
which we are going to-- how does icon work? DASH: Is there a
play icon maybe? MATT: Icon data, yeah.
icons.play_arrow. Not super exciting,
but there we go. DASH: It's a good start. MATT: It's a good start. And we're going
to have onPressed. And onPressed we're going to
do some sort of play sound shall we say. DASH: I imagine you
could have a Boolean. And onPressed it
toggles the Boolean. And then depending on
the state of the Boolean, the icon is play or pause. MATT: That's true. But we'd need a stateful
widget for that, wouldn't we? To track the Boolean state? DASH: Yeah. MATT: OK, but by the
power of Gray Skull-- by the power of
the plug-in for-- I don't know who to look at-- by the power-- DASH: Talk to the bird. MATT: I can convert
to a stateful widget. Because our tooling
team is awesome. And they added all the support
into Visual Studio Code and Android Studio. You were supposed to
say Android Studio. DASH: Sorry, I didn't know
what you were going for. MATT: And so we
can convert that. That's all lovely. Look at that. And now, we can-- you
would like a Boolean? DASH: Yeah. MATT: OK, so we'll
put in a Boolean. And it's going to be what? Is playing? Let's call it isPlaying. DASH: Yeah, MATT: isPlaying and it's
going to start with false. Because we want to keep
these things-- there we go, make it private. So what are we going to do? We're going to say onPressed
let's say we want to-- how are we going to do this? Are we going to say, if-- DASH: You can do the
question mark thing. Or just-- MATT: Oh, that's a good point. DASH: Just say isPressed
equals not isPressed. Toggle it. MATT: Yep, but should we check
to start and stop the playback? Well, we'll do that first. isPlaying is equal
to not isPlaying. DASH: Oh, yeah, sorry. I was jumping ahead of you. MATT: No, you're always
two steps ahead of me. So OK, so we're flipping
between isPlaying or isPlaying. And let's add in-- right, so if it's-- DASH: isPlaying. MATT: --isPlaying
then we stop playback. Yeah? DASH: Yep. MATT: Else-- you don't
even need me here. Why do you need me? You've got this totally covered. DASH: You're a little
faster at typing. So thanks for that. MATT: Also, let's
not be so verbose. Let's just say stop and play. OK, great. So we're going to have a
couple of private methods here, which is going to be
void stop and void play. So we've got like the
simplest stateful widget ever. Lets just wire that up. Because this will--
actually it'll work. DASH: Do you want to
change the icon too? MATT: I want to
change the icon too. OK, good point. So what is the best
way of doing this? We could track the icon here. DASH: Just question mark
ternary thing I think. MATT: Oh, yes. That's true. So we can do isPlaying question
mark ternary operators. This is good. DASH: You need a lowercase i. MATT: I do. I do. Your eyesight is awesome. So if it is playing
we want it to be-- DASH: No, you want to have it
as the play if it's playing. MATT: --stop. DASH: Oh, wait. No, you're right. You're right. Right. MATT: Yeah, because we
want to be able to stop. DASH: Yep. MATT: Otherwise, it's going
to be icon icons.play_arrow. DASH: Play arrow. MATT: OK, there we go. This is looking--
let's close this down so we can see a
little better here. OK, so if I swap this out now-- let's keep it centered. And let's make this
our playback button. Isn't that amazing? Wait, wait, wait for it. Oh. Oh, that's not so amazing. DASH: That's a pretty
sweet app we got there. MATT: That is a pretty
sweet app, except we are-- what are we doing wrong? Well, I tell you what
we're doing wrong. Because what we
should be doing is-- DASH: Ah, yes, good point. MATT: I always forget
to do set state. Let's do a full restart. Because we're mucking around
inside our stateful widget. I don't think I really
needed to do that. There we go. Oh, isn't that great? DASH: Hooray! MATT: We can make it
look prettier later on. OK, so now we need to work
out how to play some stuff. So let's go in here. And let us basically steal
all these wonderful snippets of code in this plug-in
with Flutter Sound. So I am going to drop this in to
play, just to see what we have. OK, so we got a bunch of things. And oh, look how badly
formatted that ended up. That goes there. This goes here. And this goes here. And this is all fine. But everything is all broken. So let's fix this up. First off, this will
need to be async. And secondly, we
probably need to import-- DASH: Are you missing a closing
parentheses or something? MATT: I am missing a-- no. There we go. DASH: Oh. MATT: It's just horribly broken. So where does Flutter
Sound come from? Flutter Sound,
new Flutter Sound. That's fine. Let's drop this in
here for the moment. We'll probably put
that in the state. And then we will-- DASH: Yeah, maybe states later. MATT: Yeah. Package-- DASH: Import Flutter Sound. MATT: Flutter-- why is-- because everything's
broken at this point. So let's just see if
we can work this out. Or can I cut and paste? Uh-oh. Uh-oh. Oh, that's for recording. We might not need it for-- DASH: We don't need to
record, not yet anyway. MATT: --playback. So this is Flutter Sound. So import Flutter
Sound slash, I'm going to guess it's Flutter-- oh, there it is. Yay! It's not looking too bad, is it? DASH: Looks good. MATT: My fingers
are getting tired. I really wish you could type. We're going to have
to work on that. OK, so we now have-- oh,
that's not necessary. OK, we have Flutter Sound. Yeah, OK, we've
got a path, which we will sort out in a minute. And this is a
player subscription. What is all this stuff doing? This is all set state stuff. DASH: It's red because I think
we need to define it first. Var player subscription
or something. MATT: This is going to be
Flutter Sound, Flutter Sound. This is returning a
stream of play status. Oh, so we can get
our play status. Now ,we don't really need
that at the moment, do we? DASH: No. MATT: Potentially, so
let's get rid of that. And actually, we don't
need any of this. Wait. What do we-- oh, yeah,
we start player there. So let's just get
rid of all this. And really that's all you kind
of need to start playback. OK, so the next we're
going to have to do is we're going to
have to get a path. Now, we've got to get a
path from-- look at you. We're going to have to
get a path for an asset. How do we get a
path for an asset? Pop quiz. DASH: You put-- you make
an assets directory. Or you have an assets directory. MATT: I do have an
assets directory. DASH: Stick your file in there. MATT: I have done. DASH: And then you have
it in your Pubspec. MATT: I do. DASH: So you're good. MATT: But how do I get it here? DASH: You specify the path. MATT: Don't I have to call
up an asset bundle to get access to it? DASH: Oh. MATT: I think so. Let's go check it out. This is so let's say, you
know, it's like how do we do-- DASH: Really? I thought you could just
pass the path if it just takes a string. MATT: Really? Did that work? DASH: Yeah, yeah. MATT: OK, well,
let's try that then. So our path is-- oh I see. What is this? String path, Flutter Sound,
start player, string URI. OK, so I'm thinking here
that we can just pass it a-- let's have a quick look at
the docs see what it says. Start player string URI and
we have default URI path, set your function call, default
path for Android SD card. That's if it's downloaded. We actually have it as an asset. So will an asset work there? DASH: It does with some of
the other audio players. I can't speak to this one. MATT: Yeah, I'm not too
sure about this one. Let's see. Because this says null. OK, so let's quickly
start player. OK, so when the URL path is
not set during function call on start or start
player, the path-- OK. So we could asset slash-- what's it called? What is it called? Where's my assets? Surf shimmy, OK. I don't think this
is going to work. But we'll try it. DASH: So pessimistic. MATT: So pessimistic. Let's see what happens. We've got our
terminal open here. And we press play. Missing plug-in exception. [INAUDIBLE] start player
on flutter channel sound. Oh, well, that's more of
an interesting problem. DASH: Is it because you
haven't hot reloaded and you imported the plugin? That often happens. MATT: That might be true. Let's see. Let's start this again. Let's get rid of
this error message. OK. Restart. OK, we have the exception. It's kind of jammed up. No thread with ID one. So I'm going to kill this. And you know what? Just to be on the safe
side, I can stay here. I'm going to do Flutter
packages get, just to see. That seems good. Of course, I probably imported. But I already had
it running, which means I imported it after. And whenever you
import the plugin-- DASH: That's what I
was starting to say. MATT: Yeah, you
were totally right. I should have paid
more attention. So you think this
should work now, right? DASH: Think so. MATT: Also, I keep
saying we're on Android. I'm actually using
the iOS simulator. OK, so we are started up again. Let's bring up
our debug console. And let X Code do it's magic. In the meantime, if we're
talking about plugins, the one thing we can
at look over here-- sorry, no, assets, there
is a very handy page. Adding assets and
images on flutter.dev, which talks all about
loading in assets. We can load string. And we can do this,
that, and the other. This is an interesting one,
because this is actually going to be loading in a sound. So we'll need to
work how to do that. OK. DASH: I still think
it should be the same. MATT: OK. It is not finding it. Because it's looking for a file. DASH: It's not erroring though. MATT: No, this may be the
plugin just being gracefully-- DASH: It's not very graceful. Oh, because we're not
subscribing to my error message status. MATT: We're not
subscribing to it, exactly. So we have start player. We have stop player. And these all point to false. DASH: Wasn't there that listen
one that you we had before? MATT: The listen one? That was listening
for changes though. DASH: Yeah. And couldn't it
change, be an error? MATT: Yeah, that's true. DASH: I guess we could look at
the documentation and see if-- MATT: Yes, so this is handy. Because what this
means is that when you're using this
for a podcast player and you download
your files, we're able just to link
straight to the files, like you do with some of the
image libraries and whatnot. But because I decided to
be awkward and use assets, we're going to have to find
out, how do we get a file reference to an asset? Because if we use
the assets here, this actually loads the
asset as opposed to getting a path to the asset. See what I mean? That's a yes. OK. So what we need to do is
we need to work out how to pull an asset in for this. Yeah? That make sense? DASH: I think so. MATT: OK, we're
going to research this for a minute or two. And we'll be right back. [BEEP] And we're back for
the second time. OK, so we did a little bit
of research, didn't we? DASH: Yes. MATT: Yeah. DASH: We know all
answers now, all of them. MATT: Oh, good. Well, why don't you tell us
what all the answers are. DASH: Well, so first
of all, the process for loading from an asset
thing, as Matt said, you have to load as bytes. It's not the smoothest
process in the world. However, there is code that
does just that conveniently from an IO session last year. MATT: Yes. So the problem is we
have an asset bundle. And asset bundles are an
awesome way of doing things. But they're all about
actually loading the data in from the assets. This plugin is looking for a
file URL, or a URL in general. And so we would have to do-- you know, there's a few
workarounds for doing this. What they did at IO last
year, taking a look at the-- who did do that IO talk,
Sufficient Goldfish? DASH: I helped. MATT: You helped, indeed. But it's in Emily Shack's repo. And down here we
have a simple class. And what it does is it
actually takes an asset and saves it to a--
well, temporary-- saves it to a file locally. So that when you need to
access things by file URL, you can get that. So we can use that. And that will be great. What we can also do is we
can probably just give it a-- DASH: URL. MATT: --a URL. So we can give it a network URL. DASH: Yeah. MATT: And hopefully we
can pop it in there. DASH: Especially since, you
know, this is a podcasting app. So you're not going to download
all the things ahead of time. Or you're not going to have the
things when you ship the app. MATT: Yes, we are
going to eventually, when we get around to it, we
are going to have both streaming and offline work. How does that sound? DASH: Sounds great. MATT: That sounds wonderful. So let's just pop in a URL here. And so I'll be fair
too Incompetech. Obviously, we're streaming
straight from their website, which, you know,
isn't very fair thing to do with their bandwidth. So don't do this. Because that's mean. And we're going
to go with it just to show hopefully, that
things are working. And then we'll fix
that a little later on. DASH: Yeah, we can implement
downloading it and going from there later. MATT: Yeah, and again, for
links to all his other music, look in the show notes. Right, so what do we have? We have this. We've got a path. Hopefully, start
player with URL. There we go. We're not really doing
anything with path here. That's fine. So moment of truth. DASH: It's all going
to work perfectly. MATT: I'd ask you to
hit the Play button, but felt doesn't really
work on my keyboard. DASH: No. MATT: Maybe it does. DASH: Thank you. MATT: You're welcome. All right, let's
see what happens. DASH: I have an aiming
problem sometimes. MATT: You-- you--
you certainly do. I have the bruises
and scars to show it. Yes. Add-- oh, OK. Well, it's looking
for the microphone, because this also
does recording. And I haven't
clearly set that up. So let's just-- DASH: Well, plugins often-- [MUSIC PLAYING] MATT: We got surf music. There we go. You're going to
catch some waves. Oh. Right. Oh. Oh, it's looping. And we haven't implemented stop. DASH: Make it stop. MATT: All right, hang on. I'm going to-- [MUSIC PLAYING] Oh, it's because
I'm doing set state. OK, hang on. I'm going to hot restart. DASH: Ah! MATT: And hot restart
hasn't worked. OK. I think I might-- do you think we should
implement stop next? DASH: Please. MATT: OK, implement stop. OK, so let's go over and
let's go back to the docs. Docs very handily had a-- this is great. This is the show where we Cut
and Paste from other people's docs and then spend ages
trying to get it to work. OK, so let's go over
here, and let's go to Stop, and let's go boom. And what are we doing? We are basically-- all
we need at this point is, we need to stop. Oops, stop recorder. This is going to be async. Interestingly, we need
access to flutterSound. That's not a problem because
we're in a stateful widget. So I can do flutterSound-- DASH: And just a note on why-- when Matt hit Run was asking
to access the microphone, this plugin can do
recordings and play. MATT: Yes. DASH: And right
now, a plug-in has to request all the permissions
for everything it can do. Even though we are not using
the recording functionality. So, that's why. MATT: That's a
really good point. So, we should take care
of that at some point. Now-- DASH: Agree. MATT: We're going to
have to initialize flutterSound StatefulWidget,
what's the best place to do that? [WHISPERING] DASH: Oh, initState. MATT: There, you go, initState. Let's do that in initState. OK. DASH: Please. That's a great idea. MATT: It's the best
idea I've had all day. So here we go. We have an initState,
we're going to super it, and then we're just going
to initialize flutterSound. And there we go. So, this is initialized here. And I've renamed
this to Sound just to make my life more difficult. DASH: Renaming schemes
make your life-- MATT: Easier. DASH: Yes. MATT: So we have a URL. We can probably, you know,
that doesn't have to live here. That can live, you
know, somewhere like-- let's just take it up here. So we have everything
close to hand there we go. So, in its simplest form, wow. So, for all of our
playing around it, turned out we had to do
two asynchronous calls to get this to work. We had to start/stop. So, now, let's see where we are. This is complaining
because it isn't used because the flutterSound. I shouldn't do that,
I should do that. There we go. DASH: Good call. MATT: Good call, exactly. So, this is where
the nice people who are recording this
video get irritated with us by making loud music again. DASH: Yay! MATT: Sorry. And we need to run
this, there we go. And good, so now we
have a plug-in working, we've worked out the
very basics of it. We've got our icon, hopefully
this is going to work. So why don't you have a
little think about what we're going to implement next. Because the poor,
unfortunate people watching this have a half an hour of
this nonsense continuing on. DASH: Hmm. MATT: Yes. Shall I do it? DASH: Yep. MATT: OK, here we go. And, streaming,
streaming, streaming-- [MUSIC PLAYING] Oh, no. It's broken. DASH: What happened? Ahh! MATT: Let's have a quick look. Good, so we're going to
do some debugging here. So, what I'm saying is, the
recorder has already stopped. Oh, because I stopped the
recorder not the player. DASH: That's-- MATT: Why didn't you catch that? It's almost as if you
can't actually see. DASH: That hurt. MATT: I know, sorry about that. If you could blink,
it would be fine. All right, so,
this is where like, we fast moved through the video. Up to the point where we wait
another 10 seconds for this to run. So, now that I've
stopped being an idiot-- DASH: Yes. MATT: OK. DASH: That's on me too. MATT: That was so
nice of you, I was expecting a snarky come back. DASH: Sometimes I'm nice. MATT: Sometimes you are nice. Right, shall we
try this once more? DASH: Yes, please. MATT: If this
doesn't work, we're probably going to get fired. This would be your
one and only show. DASH: No. MATT: OK, and we're streaming. DASH: Uh-oh. MATT: No, no, we're streaming. We're waiting, wow
we're still waiting. There we go, OK. Remember, my network connection
might not be the best-- OK. Does stop work? DASH: Phew. Hooray! MATT: OK. And then we have the world's
most awesome podcast app. If you install your podcast
app, Compile time, then this will work perfectly for you. OK, so we have basic
Start and Stop controls. So, why don't we do a
little bit of layout Because we're
starting a new app, we have a button in the
middle of the screen, how would you like this to look? DASH: Yes, so it'd be
great if we could have the, I don't know if you
call it album art, but whatever the podcast
little, artwork is. MATT: Uh-huh. DASH: Have that up top. MATT: OK. DASH: You could have a
little slidey, or a line, showing the progress of how far
you're into playing your thing. So you can Seek. MATT: OK. DASH: I think those are two
good things to start with. MATT: OK. So, why don't we slice
up stream a little bit. DASH: We can also, like,
jump forward, jump forward 15 seconds or backwards,
and all that jazz. MATT: Yes. I'd love to try it when we have
to listen to surf music again. But that's something to aim for. So, let's look at our screen. We have a button and
we want the controls probably down the bottom, yeah? DASH: Yes. MATT: So, I'm going to say,
let's start with a column. This is my proper
British accent, it's a 'collume' or
column, depending on where you come from. So, let's leave this
widget, that's fine. We've got this playback
button we can leave that as is for the moment. Let's create a StatelessWidget,
and lets call this DashCastApp. There we go. And disrupt a scaffold,
blah, blah, blah, all nice. So, what we're going to
have, we're going to have a 'collume', and a
'collume' takes children. Which is going to be
a list of widgets. OK, there we go. So, there's our basic layout. Now, one of the things that we
could do is, you'd like maybe, the controls to be
10%, 20% to the bottom. And the rest of
the stuff up top? DASH: Yes. MATT: So one way we could
do this is, in the children, we can put them
in a is it a flex or [INAUDIBLE] I
think it's a flex. DASH: I can't remember. MATT: Let's create two
flexes, multi flex. I don't even know what that
means, but it sounded funny. DASH: This is me being flexible. MATT: Do yoga. DASH: Yeah, doing the splits. MATT: Downward Dog. Yeah, there you go. Right, [INAUDIBLE] was I doing? DASH: Making a flex. MATT: Right, I was making flex. Let's make some flex things. OK, so we have DASH: Stay on target, Matt. MATT: Stay on
target, oh no, we're going to do Star Wars jokes. I was going to make
an angry dash joke but I do want to throw
you across the room. So, let's go and we have flex,
and I think we can give it a-- what is it? Oh wait, wait, wait, wait, wait. How does this work? Do we give it a
flex or flexible? DASH: Sorry. You're on your own on this one. MATT: There we go, OK. So flexible. So, this top one is going
to be 9/10, so we're going to specify flex of nine. And in here, what
we're going to do is, we're going to use the very
handy Placeholder widget. Placeholder widget, what
is that thing called? It's called Placeholder,
but this has to be child. There we go. Yay! DASH: Perfect. MATT: Placeholder widget. And then down here, we're going
to give this a flex of one. So, nine, one. [INTERPOSING VOICES] DASH: It's cool. MATT: And here we're going to
put another Placeholder, just so everyone can see
this is working. And then we're
going to go up here. We're going to get
rid of all this stuff. And we're going to put
in our DashCastApp. Oh! Now, couple of things. We are kind of
overwriting the top and maybe we don't
want to have that bar. So, what can we use to
stop that from happening? DASH: Safe area. MATT: Safe area, here we go. OK, so, let us
just do that here, let's wrap this in a safe area. Oh, OK, cool. Good, so we've got our area for
all our stuff, that's probably looking a little [INAUDIBLE]. OK. We can play around with it. And so, you're going to
want a few controls now, for your awesome podcast app. DASH: That's right. MATT: OK, so let's
create a new widget. And let's call this our-- DASH: Audio controls. MATT: Audio
controls, here we go. There we go, OK. So, audio controls set. Right, what's the best
thing to lay this out? DASH: I imagine you would also
want a column to some extent-- because you want your
little line, where you're going to have your total
length of the podcast on top of the actual controls. MATT: Oh, yes you're right. DASH: And then the
rest it might be a row. MATT: OK you're right because
we're going to need a column. Now, we're going to
pop this in here. DASH: You could do that
later though, I don't know. Oh, you could have a column with
one thing that's still cool. MATT: Column with
thing, it'll work. DASH: --another placeholder. MATT: Let's put it a widget
because apparently widgets are now a thing. I created a library
called Flutter 'Woodgets." They're even better
than widgets. OK, so, we've got this. DASH: They're certainly
woodier more wood grain. MATT: So this is
audio controls, what are we going to put in here? We'll call that Playback. DASH: Can you use like,
that slider widget for your Seek thing? MATT: That's true. So let's create our
playback buttons. And in here, we're
going to have centered. We'll just have our
Playback button. OK, so, this is looking
a little better. Let's get to all this because
you don't need to see that. And then up at our
Placeholder, we can swap this in for
our audio controls. There we go, and boom. So we got one there. Now-- DASH: That's a pretty sweet
looking app, right there. MATT: Yes. you notice you're too sarcastic. We can style that up
a little bit better. But you wanted a seek bar. DASH: Yeah, that are jumping 15
seconds backwards and forwards. The functionality is similar. MATT: OK, so why
don't we put in a-- let's see. So, we have Playback
buttons here. So, what we really want to
use is, we want to use a row. Which is going to take children. And this is going to be Widget. This is going to be a list. DASH: I believe you might not
even need to specify Widget. It should be able to infer it,
because you're declaratively-- MATT: Yes. DASH: --putting them all there. MATT: That will work
most of the time, there's some odd
cases, I think, where it can infer the wrong thing
and it can cause problems. But for the sake of this,
these are all widgets, so I could lose all of these and
it will also work totally fine. So we've got a row. We probably want to have
our button centered, so we can set our main
axis alignment to center. There we go. And you would like
Reverse c and Forward e? DASH: Yes, please. MATT: Reverse c and Forward e. So, we're going to say -- now, how are we
going to do this? Because we're going to have to
have state for these, as well. But for the time being,
let's just put it-- DASH: Why do they need state? MATT: Well, because
we're going to need to be able to access
the flutterSound. So-- DASH: Yeah, you could
do something like, an InheritedWidget or something. MATT: Oh we could also
we could also do that. We could use we could use some
of the other state management pieces to do that. Pull it up and out. We'd still need to wrap
that in a stateful Widget because we still need to
track the state of it. DASH: The state of what? If you're going back
15 seconds, in theory, you probably always could
at least send the message. You might, you know, go to the
end of the song or whatever. But the button might
still always be there. MATT: Yes, so the button
doesn't need state, these buttons will need
to live under where we create this state. Because we'll still need to pass
the message to there, which you don't have to even
live under it, we just have to pass it around. OK, there's a few different
ways that we can do this. DASH: Yeah, since you're
typing, you get to do it the way you're envisioning. MATT: Well, I'm going to do it
the simplest way to start with. Because how we're going to layer
state on later on, is something we should think carefully about. But for the moment, the
easiest way for us to do this is actually, just to take-- let's see how can we do this. DASH: By the way,
speaking of state, talk a little bit about
how I think about state. MATT: How do you
think about state? DASH: Well, normally, I
just start coding along and when I find that
I need something with a little more passing
information around, I try to start with the most
likely solution possible. So if an InheritedWidget works-- like in this case-- so far anyway, I
think it would work. I would do that. If I need something
a little more, I'd move up the Scoped Model. And more, there's other
things like RX [INAUDIBLE] or Block or, provider. All sorts of things
that you can move on to once you graduate to
needing more complex state options. MATT: Yes. Now, I've done
the simplest thing to break all this, actually. I've done the simplest
thing and just moved all of my button controls
into a single stateful widget. Because then I can handle
all the interactions and just change the
state immediately. And that's fine in
this very small case. But this isn't how I would
architect a larger app. So, I architect apps, isn't
that very posh, architect. How I layout apps,
and so, we're probably going to change that later on. But just because
people are actually expecting us to make
some progress here, we should probably-- I've done this in a simpler way. So, there we go. So, I have now moved my audio
controls or my Playback buttons down in here. And so, what I can do
is, I can add in icons. Emily, where did
you spring from? EMILY: Talk to the bird. MATT: I mean, OK. Now I think you've completely
broken the illusion. EMILY: Emily's knees hurt. MATT: OK. I'm not getting done
because my knees-- EMILY: No, I'm
not asking you to. Keep typing. MATT: OK, I gotta keep typing. OK, so, fourth wall. At least now you
can reach the screen and tell me what to type. We have to create an icon
and we're going to do-- this should be icon. Icons dot rewind. EMILY: Here we go. MATT: Here we go. OK, I expect that. That's going to complain
that there's no onPressed, but I'll worry about that later. And Icon button. EMILY: You're going to do
onPressed null, if you really care. MATT: Yep. EMILY: Whatever. That'll just disable it, though. So maybe you don't want that. MATT: Fast forward,
and you know, we'll just put it in
here for the moment. Because that's what we normally. Do you normally do null? EMILY: Yeah. Null will disable it, so
you can't actually press it. MATT: Like that. EMILY: No, just equals, just
take out the whole function. Yeah. But that will disable it. MATT: Yeah, the button
will be disabled. I'm curious to see how long
you can keep up this Dash voice before you completely
lose your own. EMILY: You'll be
testing me, I see. MATT: Challenge accepted. OK, boom. There we go. OK, it's not looking too bad. EMILY: It's starting to
look like a real app. MATT: Not really, but you
know, it's getting there. So we have-- EMILY: Three buttons,
people, three buttons. MATT: Yeah, in only 45 minutes. Let's add your, whats
it called, slider. EMILY: That's the one. MATT: There you go. So, let's put a
slider and again, because the slider is going
to be updated from the, state I'm going to drop it
in here for the time being. We're going to refactor
this the next time to make it look a lot nicer. So, OK. We are now at the point where
we want to put in a slider. Now, the question here is,
that I have my column up here, but again, my state
is living beneath it. So I've kind of got myself a
little backwards at the moment. So I probably want to have
created by state above it and then handle the
controls underneath it. So-- EMILY: Yeah, you
should've used my advice with an InheritedWidget. MATT: I did should have used
your advice in InheritedWidget. But again, for brevity,
let's just keep going and keep everything in
our giant state object so we can get the-- EMILY: All right. MATT: --controls going. EMILY: You're digging your hole. MATT: I know, digging my hole. So, let's put in the
column real quick here. And then in our
column, we're going to have our main access line. And again as [INAUDIBLE] center. And then we are in our-- you're nodding in agreement? OK, good. EMILY: I'm excited. MATT: Are you? EMILY: So excited. MATT: This could be
the greatest app ever. We are going to put in a slider. EMILY: Yeah, slider. MATT: Slider, OK. What does slider take? Takes value. EMILY: Does the current value
that it's at, which would be-- MATT: Give it max. EMILY: Yes. So now-- MATT: So we would
probably max to 100. Or we could do 0 to 1
and have percentages. EMILY: Well, ideally-- I don't know a whole
lot about this-- but ideally, we
should get information from the MP3 about
the length of time. And that's the min and max. MATT: Exactly. EMILY: And then the
value is where you are. MATT: Yep, or we could normalize
it to be between 0 to 1. But there's a whole different
there's a whole bunch of ways we could do that. EMILY: Yeah, but ideally,
you want the time because I believe
when you slide, you can also see the value. MATT: Oh, that's true. EMILY: So we can
start with this. MATT: OK, so why don't we-- EMILY: I can look up
getting values from MP3s. MATT: Yes, we need
to go back now because we've got
a few things to do. We've got to implement
the 15 seconds back, 15 seconds forward. And we have to
implement our slider. So, you're very quiet
all of a sudden. EMILY: I'm still here. MATT: You certainly are. EMILY: Always. MATT: Have you looked it up yet? EMILY: You're making
my life difficult. MATT: Don't peck me in the eye. OK, go look that up. OK, where are we? OK, so we have
these controls here. This is all great, but
what we're going to do now is, we're going to
add some of these. Let's hide this for-- [INAUDIBLE] bringing
this down as well. OK, so, we're
going to have this, which is going to be updating. Which you see here
when we change it, these values are going to
change, which is kinda cool. What I am going to do is, I am
going to create a fast forward I am going to create
a rewind line. There we go. And what else we got? This is going to be
updated from here. So, I am also going to
put in here, because I think we're going to need it. A double for the time being. Play head, play position
probably makes more sense. And, you know, I'm just going
to initialize that here. To 0, OK. So, then what we
can do here in value is, we can put in Play position. And then, at some
point in our listener, we're going to update
our play position and it should update our slider. So, hopefully that will work. So, let's go back quickly,
and look at the player piece. Because we had a player
subscription here. And this is playing
true, player text, and see if we can get something
out of the player subscription. So, in my playback,
this is where we're going to register
a player subscription. Player subscription is going
to be a stream of Play status. This is stream. I see stream builder in our
future, what do you think? EMILY: Sounds plausible. MATT: Are you only talking to
me when the puppets involved? EMILY: Yes MATT: I wondered why
you were being so quiet. OK, don't-- don't peck me. Good, so what do
you think of this? Now that you're
back in the game. That's it, don't
punch me in the face. Now that we're back in the game. EMILY: Yeah, it looks good. MATT: Does it? No, it doesn't, it's got
red lines everywhere. How can that look good? EMILY: Sorry, I was trying to
find about MP3 information. MATT: So, we have a-- let's
put up here-- a stream of consciousness. Play status, which is what
this episode feels like. And we'll call this
player subscription. OK, and type of value, oh
it's a stream subscription. EMILY: Yeah, its a
description to the state of how the audio is
playing, or when it's done. MATT: So like-- no,
hang on a second. So this is returning a
stream of play status. So this is returning a
stream of play status. But then this-- oh, wait. Oh, because this is
actually returning-- EMILY: That is telling
you when it's done or-- MATT: This is returning a stream
subscription of play status. Interesting. OK, this is fine. So, what we can do
is, we go da da da da. EMILY: And then listen. MATT: And then we can
do, [INAUDIBLE] oh wow. Ooh. Dot, dot. What's that dot, dot thing
called, I've forgotten. EMILY: Cascade operator. MATT: Cascade operator
to the rescue. So we can now
actually track this and we have a
player subscription. This is good. Now, what are we
going to do with this? We have a bunch of things here. Well, look, so this is our
players subscription status, which is E for some reason. If E is not equal
to null, then we have current position,
which is a double. So we can just lump all this
out and we can do play-- EMILY: Play position. MATT: Play position,
and that probably should be underscore
play position but we have access to that. We've got some things here,
like, we already have, this is fine. We don't need any of
this at the moment. EMILY: It's probably best
to set the play state based on what your player thing
is telling you, though, right? MATT: That's true. So instead of setting it up
here, where am I setting it? EMILY: Line 114. MATT: Line 114. Oh Yeah, that's true. So we can just-- oh, but we need to
make sure we stop. We'll have to reset that
because this only in play. But that's fine, because
we're still calling it stop. So let's not do any
of that anymore. There we go. So, up here we're
going to not worry about this for the time being
and we don't need to do this. Cool, here we go. So this is-- EMILY: Is this necessary there? That seems superfluous. MATT: What's necessary? EMILY: Line 84,
this dot set state. Can't you just do set state? MATT: Yep. I can do set state. [INAUDIBLE] EMILY: [INAUDIBLE] is playing. MATT: I could do that. EMILY: You can get rid of it. And this dot is playing. MATT: There was a time
when I used to swear when I was a Java developer. I would do this dot everything. My code was super verbose. EMILY: Yeah, there
is a better way. MATT: Yep, Dash hates this. EMILY: Dash does hate what? MATT: Dash hates this. EMILY: Sometimes Dash
hates verbose code. MATT: There we go, OK. So, [INAUDIBLE] just
like, keep everything. Let's keep everything. OK. That's a little better. OK, so, what do we have now? We've got play-- EMILY: We've got some
red screen of death. MATT: Oh. I'm changing a bunch of
stuff in a stateful widget. So, I am going to do that. There we go. EMILY: Oh, that
looks much better. MATT: Doe look much better. I mean, it still
looks pretty ugly but. So, theoretically
speaking, when we play now, this will change state
when it's playing. And when we start-- EMILY: And you should do-- Yeah, equivalent when you stop. MATT: There you go. EMILY: Do you need to-- oh. MATT: Tell me, you
would know this because you're a smart
Dart bird, aren't you? EMILY: Well, so on
line 81 you're saying, onPlayerStateChanged. Listen, so when you hit stop,
the state is going to change. So maybe we don't want
to set isPlaying to true. We need to see
what the state is. MATT: Say that again. EMILY: So line 81. MATT: Line 81 is actually-- OK so this is wrong. Because I'm always setting the
state to true every single time it listens. Which is kind of is superfluous. EMILY: Well, but also,
this state could change. MATT: Yes. So I could do this. Because this is going to change
every single time on player state. EMILY: What things caused
that to fire, we should we should find out. MATT: We should find out. EMILY: When all else
fails look at the docs. MATT: Go to definition. This is a stream onStateChanged,
onRecorderChanged. EMILY: Player controller. MATT: Player controller. EMILY: Line 12. MATT: Line 12. We're doing it by line numbers. Yep, we do have it and we-- EMILY: My beak is
not very precise. MATT: We have it
player controller, which we are already accessing. Because we have our-- no we don't. Now the question is, because
I would imagine from this, that this is updating
the current position. So let's see what
happens, shall we? Because we can
theoretically run this. And if this is
correct, this should-- EMILY: Do we know what the
values are for these position numbers? MATT: Oh, not we don't. We haven't set those either. EMILY: Maybe we should just
start by printing them out, to get a sense of
what they're like. MATT: Yeah, OK. So why don't we comment
this up for the moment. And let's just print e
dot current position. EMILY: I mean, you
can also set state. It just might not change
if they're really small relative to our range. MATT: So you're saying? EMILY: You can still set
state but fighter may not look like it's changing if
the values are super small. Because our range is one to 100. And the values are all
like, one, two, three, four. MATT: How about this, we have
duration, which is double. So, our position, we could
make current position divided by duration. Which puts-- EMILY: Oh, perfect. MATT: --0 and 1, right? EMILY: Perfect. MATT: Is my math correct? EMILY: Yeah. MATT: Well, let's just
print it out just to see. Now, I'm going to mute
this unless you really want to listen to some more-- EMILY: But how will
we know if it works? MATT: Because we have a
set state in play position. EMILY: You're just going to look
for the state of the buttons changing to see it works? MATT: Maybe the slider
might actually update. EMILY: Yeah, OK. MATT: What do you think? EMILY: Do it. MATT: I'm going to mute
the sound because reasons. EMILY: Because we're
tired of sea chantey. Sorry. MATT: It's not a sea
chantey, it's surf music. EMILY: Oh. MATT: You know. EMILY: I'm not a
seagull, what do I know? MATT: You're an albatross. Oh, so mean. Yeah, you go. All right, let's
see what happens, let's see what happens. So if we hit play, is going to
do its streamy thing because we should probably implement
the download in offline [INAUDIBLE]. EMILY: Yeah a future
episode is going to have quite a
bit of work to do. MATT: Yeah. EMILY: And not always
hit that website. MATT: Yes, because
that's not very nice. Oh, there we go. EMILY: OK. MATT: That's good. So-- EMILY: Oh and it's
moving, look it's moving! MATT: It is moving. EMILY: It's moving! MATT: High leg. Here we go. OK, so, there we go. We now have-- it's
not interactive-- but we have a slider. Which isn't styled. And it's moving. And we have the
right numbers here, which look like in milliseconds. EMILY: Yeah. MATT: And then if we stop-- EMILY: Hooray! MATT: It stopped moving. EMILY: Yeah. MATT: I'm assuming
if we hit Play again, it's probably going to start
back from the beginning, I would imagine. EMILY: Yes. MATT: OK. EMILY: Progress! MATT: In the state, we can
save that when it stops. So we can start playing
back from there. So we have all of the
pieces that we need now, to actually start to build out,
certainly, offline podcasting. EMILY: Have we tortured
our listeners enough? MATT: I think this may
be a good place to stop. Why don't we summarize
where we are. EMILY: OK. MATT: So there we go, we have-- where did Emily would go? DASH: Emily was never here. You've always been
talking to me, come on. Keep up. MATT: Of course. So, we have got
to the point where we have some basic
playback controls in place. And we have a audio plugin. DASH: DashCast. MATT: DashCast the
app is evolving. And we have the
plugin implemented. So, next time around, we
got a lot of work to do. We are probably going to pull
the state out and make it a little bit tidier. So we can have
different controls in different parts of the
screens without wrapping it in a joint stateful widget. Because that's not
very nice, is it? DASH: Yeah, it's not,
I tried to tell you. MATT: I know,
you're a smart bird. We probably want to do stuff
with podcasts, don't we? DASH: Yes. We want to maybe, hook
up an actual podcast. That would be great. Then we have some
great album art. And maybe, hook up
those controls properly so you can seek. It's going to be great. Oh, once we hook up one podcast,
we have multiple podcasts. Make a little list
views, select them. MATT: So-- [INTERPOSING VOICES] MATT: Yep. We have a lot of
stuff to explore. For those few of you who
are left and have borne through this entire episode-- DASH: Oh, and video. We were talking about video. MATT: Oh, we're gonna-- would you stop? Feature creep. Feature Creep. Next, we're going to have
like, Hacker News integration. DASH: Yeah! MATT: Oh, no. DASH: And then you
could call the creators. Use your dialer. MATT: So-- DASH: And it can have
Google Maps in there. MATT: Are you done? DASH: Can we add the app? MATT: Yes, yes we can. That would be fantastic. DASH: Great. MATT: Great, and we'll
have an IOS theme, as well. Because Flutter makes
all that possible. DASH: Yes, please. MATT: OK. So-- DASH: And then we'll
[INAUDIBLE] podcast. [LAUGHTER] Can I have my own podcast? MATT: If you-- DASH: Please? MATT: If you stop talking,
you can have your own podcast. DASH: OK. That's kind of counter
to the podcast, though. MATT: So, from , us thanks
very much for watching. SPEAKER 1: Hey, you
should definitely subscribe to the
Flutter YouTube channel, which is full of these video-- full of these videos that's-- that are similar to this one. You should, kind of, subscribe.