Building DashCast, a podcast app in Flutter (The Boring Flutter Development Show, Ep. 19)

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
[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.
Info
Channel: Flutter
Views: 33,705
Rating: undefined out of 5
Keywords: Flutter audio plug in, Flutter audio playback, Flutter multimedia, multimedia app, podcast app, audio playing app, audio playback, audio plug in, Flutter BoringShow, Boring Show, Hacker News app, google developer experts, flutter tutorials, flutter demonstrations, code flutter, flutter tutorial, google flutter, flutter development, flutter mobile apps, mobile development flutter, mobile development, developer tutorials, flutter tips, flutter code tips, GDS: Yes;
Id: oyy_1CjNdBU
Channel Id: undefined
Length: 62min 22sec (3742 seconds)
Published: Mon Apr 01 2019
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.