Playlist Builder - Part 2, Building a .NET MAUI app from scratch

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
foreign [Music] Series where I build a dotnet malware app from scratch today I will continue where I ended last time so last time we built the login view or actually we loaded the Spotify login View and we acquired the token so today we'll restore the token in a secure way and we will build our first real view a search View so let's get started with that so let's open Visual Studio so here we have the code where we ended last time so the first thing we will do is to store the access token and the refresh token in Secure Storage and we have built-in support for that within.net Maui but I like to wrap that in a separate service class and that because we want to have the code testable and we don't want to use secured storage in tests so we create a separate service for that and that also makes it easier if we want to switch to another Secure Storage for example if we will take this code to Blazer and a website so let's do that we create an eye secure service so first we create an interface I Secure Storage service and that one will have three methods save contains and get and now we can go and create an implementation for that so new class secure store storage service and add interface AI storage service add the methods and contains then we can do like this Secure Storage dot default Dot get async because it will not have a Secure Storage built in so we to get async we pause the key and then we do like this uh more result await okay so if result is equal to null then we return false otherwise return true and we need to add async keyword up here because we have an await method inside of that okay so is that so now we can go and use get so that will basically be the same so we can copy this code we add async keyword here the reason I want the contain this method is sometimes you just want to check if you have a value there or not so instead of returning false here we will instead throw an exception throw new key not found exception no key in secure storage so and otherwise we return result just like that so and now we have one method left and that is just save and that one is simple await Secure Storage dot set async pause the key and the value just like that so as you see Secure Storage in the malware is really simple so before we forget that we go and add this to the ioc container and we do add this as a Singleton as well so Builder Services dot add Singleton I say cure storage service and map that to Secure Storage okay now we can go to the Spotify service and we can add this to the Constructor so injected layer so Spotify service and I Secure Storage service add it to a private read only field and now we can save the access token and the refresh token so we can use them later so cure storage service Dot save and now we can use name of result.access token as the key for this and then we pause the access token and save it if we need a weight wait just like that copy the row and so we can save the refresh token too so refresh token refresh token yes so now we have saved that to Secure Storage and now we can go and create another view the first view of the app so let's go and create the home View so we go here we add a new file.net Maui content page saml and we call it home View and for that we need a home view model that and with view models you know that they should have the view model as their Base Class we create that one in the first episode and that is just empty abstract class right now that have tiny view model as a space class but I always create those view models because I often want to add something to them later and if I do that from the beginning I don't need to go and change base view model for all my view models so that's the reason why I have it there so now we can go to Maui program and we can add those view model now also so we don't forget to do that later and we'll get an error when we try to run this app so home view model and home View so and now we can go to the view and inject The View model into it because this is also something that I often forget and then I will get an error when I run the app so let's do it from the beginning so I don't will do that later so home view model home Remodel and you can add that in a field also because we maybe want to use that later and binding context is equal to home view model just like that and now we will also remove content page here because we are using tiny MBM and Tiny MBM has their own base view so let's move content page and we go to the sample and we will change the base Clause you only have to do this once so you don't need to do it both here in the thermal and in the code behind but you cannot only do it in the code behind but you can only do it in the saml so this reason why I do that so XML and S VM and we import a tiny V namespace so and then we have mvvm and then we have tiny View and the reason we want to use this is because this would help us wire up some stuff that we can use in the view model for example initialize or write on appearing on disappearing so we don't need to call that ourselves from the view model but I will show you how to use that later in this video so now when we when we have created this views we can go and add them to the Shell and now we have the login page that are the default page when we start the app for the first time but we will also go and add a tab bar because that's something that we will have in our app when we have logged in so you create a tab bar right now we will only have one tab but we will add more tabs later during this series so we create a tab bar with shell content title home and we add a route home and we will also add a route to the table bar and that can be main for example uh I used to use that for the main content of the app login will be when we start wrap but that will not be our main part of the app so then we have title wrote and now we need a Content table template to map the page exactly as we did here so we can copy that one and instead we say home home view here so now we can format the code a bit nicer and we're done with that but I also want to have an icon so let's go and add one so add existing files because I already have one icon prepared here so we have this is a home SVG and if you remember when you add an SVG file to a malware project it will compile into pngs so here in the icon property we will use IC underscore home dot PNG like that and we also need to go and verify that this is a Maui image I don't really know why they said this is a bundle resource by default it should be Mao image and now when we have this we can go back to the login view model and we create a navigation here so here we have the initialize call to the Spotify service and let's add a variable to where we can check if this was success so if result is true then we can navigate and navigation is part of the time MLM is more or less just a wrap around shell so you don't need to use shell dot current in your view models because I like to have my view models clean from stuff like that so we navigate and we then navigate to Main as the route name of the tab bar was so let's go let's go and run the app last time I ran it only on the iOS simulator so my ID today is to use the Android M later so that you can see that it works on both devices so let's run so here the app is we need to log in again or actually we don't need to login we just need to accept this because I already logged in so we can click the button below there and nothing happens so we set the breakpoint here and we add an exception catch point so we will stay on all exceptions and then we open up again click login Spotify okay something's wrong so we need to restart that okay we can just go continue with those errors because you will always have them when you start up but they are nothing that affect the run of the app so we can continue with that so okay login Spotify click the button at the end we need to fix the padding there later okay result true so that means that we have an access token so probably something is wrong with the navigation so let's step it through navigation has not been initialized okay I know why we get this one okay then that is because we're using tiny MBM but what we never did last time was to add this row the in the to the Builder head use tiny and with them and what that does is that it sets up the navigation services for us and everything that we need for tiny MVM so now we can try and run this again button there we can remove the catch point because we hopefully don't need that one now M laser login click it through okay we are at the home View and as you can see we have no tab bar here right now and the reason for that is because we only have one item there if we add more later it will show up so but if we have acquired an access token already we don't want to go via the login View so let's go and check that when we start that so we go to the login view model and here we add an override of initialized and this initialize method will run every time The Binding context is set if you have tiny view as the base view of view so let's go and check if we did that in the first episode so yes we have tiny view as the base Clause so it should be fine to write the code there so let's go back to the login View and add the async keyword here and a wait there and I prefer to always keep the base course and often in the beginnings because we want to change what happened after that you can also do it in the end depends on what you want to do but in most cases I have the base course in the beginning of methods so now we can go here and we can also inject I secure storage service here and add a field and we can now check if we have an access token so if a weight Secure Storage service dot contains and auth risks result I think what's the name of the class yes we also need a name of if that should work name of auth result dot access token okay if we have one saved we can go and navigate to the home View and and right now we will not handle the refresh token and if the access token has expired we will do that in a later episode so we just go here and navigate await Navi education Dot navigate to and we will have the same navigation here as we had in the handle off God method here so now we will come directly to the home View and that means that we can start to create the home View so let's go and do that so if you have used the Spotify app you probably know that they have a search view a research bar and when you click that search bar it will move up to the title view this is not exactly a clone of the Spotify app but I think that was pretty nice thing to have so we will do the same in this app so the first thing we need then is a search bar search bar so we just add that to the page and what we need to do now is to listen to when this search bar get in focus and when that happened we want to run some code so we go to the home view model and we will add a property that is called is searching and when that property is set to true it will remove the search bar on view and show a search bar in the title view so we create that property first private Bool is searching and we add the observable property attribute there so it will generate the properties for us now we will have an error because it should be lowercase and we should also have this partial because when we have Source generators we need to have partial classes okay and then we also need a command that we can bind the focus to or I should say we can bind the focus event to so let's say private void because this don't need to be async we should just set a search property so we say start search so is searching and remember is really important to use the properties here because you want to trigger an on property change event and if you use the field it will not do that so let's set that to true so now if we go to The View we will realize that we don't have any Focus command we only have a focus and event and you cannot bind it to events but in the community toolkit at Maui we have a behavior that is called event to command behavior that we can use for this use case and that is what we should do now so we go to dependencies at manage nuget packages and we will search for community toolkit dot Maui and this is a really usable package there are a lot of nice stuff in there so I really recommend you to take a look there if there are something that you need okay let's see what we need to do yeah we need to call the juice Community toolkit method in the malware program okay we do that uh can you do that after use tiny mbvm here so and then we just need to add that namespace to see if we should see we can help us with that yes it could so we make that Global using as well so we don't need to add that on other places okay now we're fine so now we can go back to our home View and now we need to import that namespace and it should be like this ensemble and when we want to use behaviors in saml we do like this search bar and search bar dot behaviors and we have tool kit colon and command Behavior and then we have event name property and that is fo cused and now we can bind to the command we want to use so binding start search more but if we want intellisense here we can also do that and then we need to set data type and there are also another reason for that is to get the buy list combined compiled to have better performance so we do that view models uh X colon data type is VM colon home view model and now you will see we have intellisense here so start search command of some reason it will not find that command but I'm pretty sure that it will be that name if we not sure about that we can try to build because we will get the build error if it's not correct but we have another problem we need to close that one okay now we can try to run to build it and see if some if this works by name Property Search Command not found okay so we go here uh of course we need to have this relay command attribute there and now it should work because now the source generators will kick in and generate that command for us so what community toolkit also have is um what community to look it also have is a converter that invert Bulls because you cannot say that for example is visible and then we have binding and we have in this case is searching we cannot say that it should be uh that is we cannot say that this should be visible when it's searching is false and then we need to use a converter or create a isn't not searching property but in this case I prefer to go with the converter so to do that we need to add the converter to a resource dictionary of the page so content page dot resources and the reason that we can use content page here is because tiny view has content page as one of its base classes so then we can say toolkit dot in vert Bool converter and then we need to give the key so just in word Bool and now we can go and use that one here so it's searching converter is equal to and then static resource because this will be a static resource now and we say in word boo and this means that this search bar will not be visible anymore when [Music] is searching is true so now we need to go and create the search bar in the title View so let's do that and we will do that by saying and we will do that by using the attach property on shell so shell Dot title U and here we can create the content that we want to have in the title view so a search bar and that one should only be visible if it's searching is true binding is searching like that okay let's go and verify that this works by running it in the Android emulator so here we know how so here we now have the search bar and now if you click it it will move up here so that works and that's fine and now we can go in write some text there probably not that nice to have it black so we can go and say text color is static resource and let's use the accent text color because we maybe should change the background color here also to the accent color later but let's keep that for now let's change the text color so it will be more readable okay now we have that it's time to start searching so before we go and build the UI for that we will go to Spotify service and use and build actual search code so we will go and add a new method to the interface that is search and we will have a search result model and this will take a search text in and also types and types will be like albums artists and tracks you will see that soon so we need to go and create this search result model because we don't want to use Android app.appster.search so we remove that using and we can also remove that system using and we go and create a new model and call it search re salt and you don't really want to see me writing that model so I will just copy it in here otherwise this will be a very boring video so right now we have it here so we only need to go and fix that using so now we go back here and we add the correct using and we will add that one to our Global using because this is something that we will use on more places so add it here and add the global keyword there and now error should be gone yes so now we can go and start to implement that method in a Spotify service so when we will use a HTTP client we want to reduce that one for all connections where we have the same headers you can use the HTTP client factor for that one but we will do it simple right now and just create a HTTP client inside of here but we create a private field for the HTTP client call it client just like that and we also go and create a private method that we can use in all our methods inside of here to get that HTTP client and that make it easier for us to refactor this code later if we want to so private HTTP client get client so if client is equal to null we will create a new client other ones we will just return the client object so client is equal to new HTTP client and the HTTP client will have a base address there is a Yuri object with api.spotify.com video one and after that we will also add the authorization header with the access token just like this an access token is something that we already have here in the field since last time but that is not really true because if we already have saved it and we have navigate here to the home view without running the initialize method we will not have that set so we need also to set that one here so if excess token is equal to null we go and read that one like this and then we need to make this async toss HTTP client and get client just like this so and we know that we have it because otherwise we'll never reach this code and that's the reason why we don't need to run a contain here okay so now we are ready to start searching so first we get the client from the method below await and now you can see async math the async keyword are added automatically sometimes that happens I don't know really why it won't happen all the time but yeah it's nice when you don't have to write it yourself so when we have the client we should just call get async and we will add search inquiry string and also the types just like this and now we need to go and fetch the content so War content is equal to Avail response content and read as string async and now we will hopefully get Json string back at least if it is a successful response and we can check that so let's say response dot ensure success status code and this will throw an exception if we don't have a success status code but if we have we can continue to serialize or I mean dear serialize the Json string so what result is equal to Json serializer dot d serialize and we want this to be search result and we pause in content as an argument and return result not respond resold so now we are done with the search method and now we can go to the view model and use this one so here home view model R again so then we need a property that we can bind to the search bar so we can get the text from there and we also need a command a Search Command that will run when we hit the search button so we create a property private string search text and observable property attribute to it we create a new method private async task search and when we go add the relay command attributes here it will create this as an async command and if we use void here it will just be a regular command but if we use async task it will be an async command that is also part of the community toolkit.nd now we can set is busy to true because we maybe want an activity indicator in the view that indicate that just something is happening and then we need to set is busy to false when things are done and we should also add try catch here so we can catch if something goes wrong and set is busy to force often that if we have a species true inside of the try cache doesn't really matter but we can keep it inside of there and then we catch an exception just like this and now we need to do something with this and this is something that I used to add to this view model the base view model that we have so right now we can just create a protected so we only can call that from classes in highlights from this view model so protected async task handle exception and right now we can only write this to the console then we can add more proper exception handling here later of course we also need to take the exception as on parameter so right line now we should just use the right and then we'll post exception there so I now have a warning because we have async keyword there so we can remove that for now and then we can return task dot completed task so and now we get rid of all the warnings so let's go back to the view model and we call await handle exception yes so then we can keep the viewers pretty clean and handle this in the base so we don't need to write this code every time we have a try catch so now we can go and call the search method result ah of course we need to inject the I Spotify service into here that and now we can use that Spotify service dot search text and types you can create that in a separate variable above so types artist album tracks and my ID is to show all of them in the search result so we have a collection view a top of artist that is horizontal then we have a similar one with albums and we have a vertical with tracks or songs at the end of The View but we will see more of that soon when it comes to that so we should also have a weight keyword here and now we will have a search result and we'll get the search result model back but for the UI I prefer in this case to put it in a separate view model that I prefer to call search item view model and right now we can add it just here let's see we can simplify the namespace type file scope namespace then we can just add it on the end at the end so we have title subtitle and image URL and a tab command and the tab command is what should happen when we press album a song or an artist so and now we'll go and map this search result to three separate properties one for artists one for elbow my one from track and that should be a observable collection for each so that we can use them in The View so we'll add those properties we should move this service read only to top because I think that looks more nice to have them there so okay so I created an observer collection for artist album and tracks and those have the observable property attribute and that means that a property will generate so what we need to do now is to set those properties but first we need to have a tab command for each of those types and in this episode we will only have empty commands and then we can start to implement this in upcoming videos so if you don't want to miss that subscribe to my channel because hopefully I can put one of this episode out each week so let's go and create three empty relay commands and we can do that after this search mode just like this so and now we can just also copy the code I already written for mapping this up because this is something that is pretty boring for you to look not related to Delta Maui so I just paste that code here and you can see I set the artist property albums and tracks with title subtitle and image for tracks and just the title for artists and albums okay now we are ready to go and do the fun stuff and that is to work with the view so we go to the home View that we only have the search bar right now so one problem we have right now is that um we don't get the keyboard up when we press the search bar so we need to set focus on this search bar up here in the title View and that is not that simple because we want to handle this from The View model and the best was if we can have focus when is searching is set to true but there are one way to handle that and that is go and create a behavior we used an event to command Behavior here but we can also write our own behaviors so let's go and do that equate folder for behaviors behaviors create a new clause in there and we name it Focus be havior and behaviors should have Behavior as a base class and here we can see we have two different options one generic one and one that are non-generic so we'll use the generic one and we'll say View and set this to view means that we can use it for All Views not only the search bar so we need a bindable property because we want to bind to it so we will create that and that one should be public that thick and this will be able to type bindable property and this property name will be is focused and that means that the bindable property will be is focused property like that and now we can stop here and we can go and create the actual property that will look like this public pool is focused and it will get the value from its focused property and it will also set its focus property in the sector so we can continue to write this one bindable property dot create now we need the name here of the property so we use name of so we don't miss a spell something and we set the type to Bull so type off bull and the declaring type and the declaring type is the what class we are writing this in so type of Focus Behavior and that is so and now we will go and do two overrides one for on attached two and one for on de-attached on the editing from and that is when we add this Behavior to control and when we remove it and we will use the on the attached to on the attaching from to reset some states so override void on untouched 2. and override on the attaching from and here you should use the one with the view we're not bindable object if you use pineapple object we need to cast it to view so we can use this one directly when we have this generic Behavior so now we will create a private field that is type view and called current View and in the online touch 2 we'll set current view to bindable and in the on D attached to we will set current view to null just like that so the reason why we set a current view field is that we want to use that in um property change listener so we go and override on property changed and now we can check if property name is equal to the name of is focused and is focused is true and of course current view should not be null and now we can set current View dot focus and call that method to set the view in focus and the only thing we need to do now is to make sure that this is focused properly has the correct State even if it's changed in the UI so let's add on focused listener to this current View and actually we need both an unfocused listener and an Focus listener so focused is plus equal to create the method for that and then we do same for current view Dot unfocused so and we add those and and unfocused ones that is focused to false and focused set is focused to chew or actually we can do like this we can use the same Focus changed and use e Dot it's focused instead and then we can remove this one and we can add this listener to both of those events because the signature will be the same and the last thing we'll do is to unsubscribe here on the attraction from okay now we are ready to go back to this ammo so to this search bar we will also add on Behavior but this time it will be our own behavior and we also need to import the namespaces for that and behaviors play and behaviors so okay so search bar dot behaviors and one problem here is now that the behavior will got will not have any binding context but we can solve that by setting uh binding context but for that we need to have a reference to this page so we go here and say x equal name is this page for example you can set whatever you want here and then when we create Behavior dot Focus Behavior we set binding context to binding and then we set Source here and Source will be this page so X colon reference this page and now we can set path is binding context and this means that we are mapping The Binding context of this page into this Behavior and now we can say is focused bind that to this searching so when it's searching is true it will set focus on this search bar so let's try if it works so now when we tap this one you can see that we have the marker blinking up there so we have Focus okay so now we can go and add the rest of the view so first we want to have an activity indicator in here so we can see that the search are working so we will wrap everything here inside of a grid and add some padding set that's one to ten for example and like that and then we add the activity indicator is running is bind to SBC and we will have that in the middle so horizontal options Center vertical options should also be Center so we will have a scroll view so we can scroll vertically in this View scroll View and now we will add a vertical stack layout again and inside of this one we will add three different collection views and one of them should be vertical the one at the bottom and the other one should be horizontal so we can swipe horizontally to browse the artist and albums so let's go and create the first one collection view dot item source and we bind that to artists and the items layout for this should be hurry sonto list and then we should have an item template and the item template here would be the same both for the artist and the album and that means that we can reuse that one if we want to but then we need to go and Define that in resources so we create a data template up here data template and inside of that data template we will have a vertical stack layout because we will have an image and under the image we will add a label with the artist name or the album name if we feed it with albums instead of artists so vertical stack layout we will have padding because we want to have some padding on the right side of the image so with that zero zero ten zero because this is the right value and then we will have an image and the source that will be image URL to get some intellisense here we can set data type to VM colon search item View Mall and this is also good to do for performance because the binders will be compiled so image URL like that and we can set mode one time because this don't need to listen to changes that will handle by The Collection View and we'll set a width request to let's say 150 and we also set aspect to aspect fill to fill out a whole space but still keep the aspect so we should also set a height request and that one can also be 150. uh we also need a key here of course where we have something in a resource dictionary so we can say this should be uh item template okay now we close the image element and we add a label text binding title you can have that one also to one time and then we set like that okay now we are ready to use that one in the collection View so here we set item template is static resource item template just like that and then we can also close the collection view like that but we also want to have a label here for a header let's say artists and for that with a font size to let's see title and because we want the albums to look the same we can copy this and we can just change to albums and albums here in the bindings too and we should be ready to go but before we do that we'll also set is visible here to binding is not busy and that is a property that we have from the base view model from time MBM so that means that we don't need to use invertible converter on is busy because we already have it's not busy property that we can bind to so let's go and run the app once before we add the tracks okay so here we have a problem we should also add um has result property that we can use and I also think that it will be good with some spacing in this one so that's spacing 10 so we will have some space between each item in this vertical stack layout so we go to the home view model we create a new property or actually we create a field that will generate a property so as we sold property and now we can go here and set that one to two as result and use the property is important to so and we go back to the saml and then we set binding here possible binding as result okay just like that and now we can run this app again and see if things works better yes we have it here okay let's go and search for Tom for example oops nothing happens and that is because we need to wire up the search bar to the view model so here we are inside of the search button the title view so we say text binding search text and command Search Command is binding to search command okay now we are ready to go so let's restart that and we'll search for Tom hit see again and now we have a result we have the artist here we have the albums and now we can just go and add the tracks in the final collection View so collection View item source binding tracks and now we don't need to set the items layout because vertical list is default and this template will be separate from the other so we can just Define it in here and move it up to the resource dictionary if we want to later if you want to use it again so it will be item template where the data template is it data types and that is still the same so search item view model and now we instead add a horizontal stack layout because we will have the image to the left and then we'll have the titles to the right so we can use a horizontal stack layout or we could have used a grid here too if we want to but right now let's go with the horizontal stack layer so image source binding image URL still use one mode one time for mode because this will not change and then it's better for performance to have it one time binding because there are no event listeners so the image should be on height 30 and also width to 30. and aspect should be aspect fill just like that and now we can add a vertical stick Cloud inside of here because we want to have both the typo and the subtitle so label text binding title from font size header and vertical options to no actually not set vertical options there which set vertical options here to Center instead just like that we copy the label and add another one and change the subtitle for that one and font size can be caption instead okay we are ready to go let's run the app so we can search for Tom again we had the result we still have the albums but we also have the tracks so what we're missing now is some spacing and padding so let's go and add that so we do that here on the vertical on the horizontal stack layout so spacing 10. and then we want the padding in the end so padding zero zero dot ten like that it is updated and it looks much better so this was everything for this episode if you like what I'm doing please subscribe to my channel and like this video and we'll see you in part three bye bye
Info
Channel: Daniel Hindrikes
Views: 1,667
Rating: undefined out of 5
Keywords: dotnet, dotnetmaui, xamarin, app development, microsoft, ios development, android development, ios, android, windows, macos, maui, visual studio, visual studio for mac, mvvm, tinymmvvm
Id: vEkLzHyhbOc
Channel Id: undefined
Length: 62min 24sec (3744 seconds)
Published: Fri Feb 17 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.