Building Your First Wear OS App with Jetpack Compose - Full Crash Course

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey guys and welcome back to a new video and today we're going to do something different we are going to build a smartwatch app using wear OS for those of you who don't know what where OS is that is basically the Android version for Google smart watches and you can already see what we will build here so that is an emulator here a smartwatch emulator and we're going to build a little stopwatch so we are going to see the stopwatch text here a little time text up there and if we click play then it just works like a normal stopwatch we can pause it we can resume it and we can also reset the timer and play again so this video will be perfect if you've never worked with Will as before and built an app for that so you will learn how you can set up this emulator you will learn how you can set up a where OS Project before and then how you can write the code to make this work and how you can also make things look like they look and before we get started with the video a quick little reminder that there is an ongoing Easter sale active for all my premium courses till April 12th you can get 30 discount on all of my paid courses I really never had a bigger discount before and to apply it you can simply use the coupon code Easter 30 during checkout and does Discount not only accounts for the single courses but also for the already discounted course bundles so if you've been thinking to get one of these for a while then there is no better chance than now so do check them out by clicking the first link in this video's description and now let's get started with the video first things first you want to go into Android studio and create a new project we're going to go to file new new project and then this dialog will open up and usually you would choose empty compositivity here if you were to build a normal Android app but we're not building a normal Android app we're building a where OS app and that is um why we need to go to where us here where where OS here in the templates and choose empty compose activity because we want to build this up in Japan pose this already has full support for a jetpack compose and composables which is great this would be the XML equivalent which just comes with a blank activity this would come with a no activity at all so you would need to implement the initial setup and this watch face is basically the design for yeah the face of your watch so when you want to see what time it is then there's also additional information that is shown on these watches which you can also design so you can decide how this looks like what kind of information the user we'll see as well and things like that we're not going to do that today we're going to use an empty compose Activity Click next and call this where OS um stopwatch for example something like this click finish and then let's first of all take a look at the structure we got here we have a normal Android app package here and the structure will feel very familiar to you if you already know how to build Android apps because this is effectively an Android app there are just some specifics that will differ a little bit because um seeing something on a watch is of course a little bit of a different user experience and then seeing something on your phone so all the UI components they will look a little bit differently and it will have some additional UI components which are specific for smart watches but we would go through these things in this video if we open main activity however then this is the initial thing you will see so you will already get a wear app which in the end is just a column that centers a certain greeting and if we launch this you will see that it actually doesn't work because we of course need a smartwatch emulator to be able to launch a smartwatch app if you have such a Ware OS device then you can also of course launch that on your real device which is deal I don't have that so I need to create an emulator for that if you don't have the emulator yet you want to go to the avd assistance so the the device manager open this up um and then want to create a device want to click on where OS and you can have different variants of this so some are rounded some are rectangular some are squares I will use the small round one so it will look like this we can then click next you can delete this at Android R download that if you don't have it yet click next and then create your emulator here I already have that so I won't create a second one but once you do have that you can select it here my whereas small round API 30 and then launch this app to see what we actually get from this if we take a look here right now my stopwatch is still showing but after the Gradle build is finished we should be able to see our little initial greeting and as you can see it says from the round World hello Android and you can already see that there are some specifics here to smart watches because we do need to differentiate between round watches and rectangular or Square watches because for round watches we of course have a different screen where our app might look differently so these are just some of the challenges we have with our wear OS development but let's get into it and start developing our stopwatch for that I will delete everything we have here um and by the way this is just a string resource which differs based on the type of the watch so you can have different versions of your strings XML depending on if it's round or not but just a little info we don't need this here let's just remove all this code that was initially generated also the preview and this wear app and start with a completely fresh setup here let's also get rid of this comment and first of all what really won't differ is the type of architecture we will use we are just going to have normal view models for wear OS app so in our presentation package we can create something like a stopwatch view model for example create that class make that a view model and in this viewmodel I will just put all of our stop order logic I would actually only use flows here because I like flows very much that doesn't mean you have to implement a stopwatch like that there are also many other ways where you don't use flows but I think it's a cool practice so you can also explain a little bit about reactivity and how we can use such flows to easily build a reactive and bug-free stopwatch first of all we want to have a state flow that saves our elapsed time so how much time has already passed for the current run of our stopwatch that will just be a mutable State flow initially with zero milliseconds and then I want to have something called a timer state that will be an enumclass we can create so in our presentation package new class timer state select enumclass and this will basically just differentiate the different states our stopwatch can be in on the one hand of course running we have paused and we have a reset so the initial state will just be reset if we then click play we switch to running if we then click pause we switch to pause if we then click play we search for running and if we then click reset we again switch to the reset State and in our view model we will just save the current state in such a state flow so that's a mutable State flow initially with timer state DOT reset we're also going to need this time of State in our UI so we want to expose the immutable version of that so timer State as state flow like this so we can observe and collect this directly in compose so now for the timer logic I will have a private function called get timer flow which takes in a Boolean whether the timer is running or not what this will return is is a flow of type long and the purpose of this flow is the the job of this flow is to Simply keep a loop active as long as our Tama is running and consistently emit the time differences so for example every 10 milliseconds we want to emit that time difference of 10 milliseconds so we can add it to our stopwatch time so the way this will work is we're going to reach on a normal flow using the flow builder in which we can now always call emit to trigger an emission we're going to have a start Millis timestamp here where we just save the current time ability so the current timestamp where we start the timer and then we're going to have a while is running Loop so as long as this is running Boolean is true and then we're going to have while is running Loop so if that Boolean is true then we want to execute our timer logic and this might seem a little bit odd to you in this implementation here since if we pass True to this function once this parameter will never change of course so this will effectively be a while true Loop in that case and if it's false it will not go in the loop at all but later when we Implement overflow operators this will make sense and I will explain that of course in this while loop we want to have a current Millis timestamp so in every iteration we now want to again save our current timestamp and then calculate the time difference compared to the last iteration of that Loop so if the current milliseconds are larger than our start milliseconds difference is current Mill is minus our start millisecond so just um the time that has passed since or between our start Melodies and current release and else we're just going to assign 0 to the time difference after that we can emit this time difference so the potential flow collectors of this flow will then receive this for example 10 milliseconds and they will receive this emission every 10 milliseconds after emitting we want to assign the next current timestamp to our start release so system current time millise and then we finally the latest Loop so that we just don't have these emissions too often but rather only every 10 milliseconds so this way we again assign the next timestamp to the start milliseconds we wait 10 milliseconds and then we again check okay what is now the time and we again calculate the difference and emit that difference this is just the most accurate way to make a timer because even if this delay would take a little bit longer than 10 milliseconds we would still be calculating the real time difference it took for this iteration and we would not assume that we always delay for exactly 10 milliseconds and now what we really expose to our UI is simply the text for the stopwatch and for that we need a formatter so private wealth formatter which will take our timestamp and format it into the pattern we want to show in the UI so that will be a date time formatter off pattern and the pattern will be first hours than a colon minutes seconds and three digits for the milliseconds that is the pattern we need to use and now we can use this formatter in another flow this flow will be called stopwatch text and it's equal to elapsed time dot map so this will be triggered for every single time our elapsed time State flow changes right now we haven't changed that at all but we will do that one step afterwards and in here we then get the long value of the total elapsed time so let's call that Millis and in here we want to map that to a different value which is simply a local time object which just reflects a specific time of Nano of a day so we can construct that with the nanoseconds of a given day and since our Millis represent milliseconds of course we need to convert that to nanoseconds by multiplying that with 1 million because one millisecond is equal to 1 million nanoseconds and then we can say we format that with our given formatter finally I want to say state in to Cache this value and convert it to a state flow we cache it in the v-model scope we say sharing started while subscribed pass in five seconds so that this block here will be executed five more seconds after the last collector disappears and we oops we want oops we want to pass an initial value of just our initial um stopwatch text like this so with this approach we really achieve some form of reactivity because whenever this elapsed time State flow changes it will automatically also cause an emission of our stopwatch text and update the UI with the new text however right now this elapsed time State flow never changes because we never did that and right now we also don't really use our get timer flow function and launch this flow to make this while loop actually run we want to do this in our in net block and in here what we want to do is want to have our timer State and call that flat map latest on that and I will explain all that in a in a second let's just pass in our get timer flow function in here we get the timer State here and is running is simply if the timer state is uh timer state DOT running let's add this experimental annotation by hitting Alt Enter on flat map latest there's one here to our review model then the warning will go away and I can finally explain what the heck happens here so again similar as above here where we set elapsed time that means we now make this flow that where we apply this flat Mount latest operator we make that dependent on our timer state that means whenever timer State changes to any other state for example we set a post or running then all these flows all these operators that come after that will be executed so whenever that changes let's say it changes from pause to running then flat map latest is executed with the new timer state which in this case would be running and we map that to another flow and the flow we mapped this to is our get timer flow so since we changed our state in this example to running this time our state would be equal to timer status running so we pass in true for this value which means this while loop will simply run and all the emissions this right Loop of this flow here causes at this point for example will then be sent into the flow that flattenable latest returns and as soon as our timer State switches to either post or reset where this condition would not be true anymore then the return flow would simply be an empty flow because it would not even go inside of this while loop so I hope it now makes sense while we pass this is running pool in here even though it never changes because with every single emission of our timer State we just construct a whole new flow and again there are thousands of different ways of implementing a stopwatch I just like to use flows a lot and I think many other developers just don't recognize the capabilities and possibilities of flows yet so I think it helps to go into that a bit as well however with that we're not done yet we also want to do something with these emissions because in the end this just returns a flow of long here because all the emissions of over get tons of will not be sent into this flow and we're going to want to do something with these we're going to say on each and we now get logs in here which is the type of emissions the get timer flow function returns here so we now get the time difference every single time the timer flow function here emits something so this will usually be called every 10 milliseconds this on each function and then here we simply want to update our elapsed tunnel because every time we get this emission we know we simply need to add that value to our elapsed time and we want to say our elapsed time is now it so the current elapsed time plus the new time difference so every 10 milliseconds we just add these 10 milliseconds on top of our already existing elapse time and then we simply say that launch in to actually yeah cause this to execute and run in view model scope and then automatically whenever this elapsed time changes by calling update this will again trigger an emission of our stopwatch text and that is just the cool thing about reactivity which we can achieve with flows that we don't need to worry about updating our stopwatch tags manually now whenever this changes because it just happens automatically as soon as we change our elastic time because our stopwatch text isn't directly dependent on the elapsed time this should only change if the elapsed time changes so now the rest of the video will be a lot less complex than these flow operators something we still need in this view model is a function to toggle is running to Simply switch the the timer State and here I want to check if the timer is oops the timer state that value press Alt Enter tree at the remaining branches if the current timer state is running and we want to toggle this state we want to update the timer state with timer state DOT post and for both these cases when we're either in the post state or in the reset State and we want to toggle the is running State we update that with timer state DOT running so we switch back to running State and last but not least we need another function which is used to reset the timer so when we click on our reset button then we simply want to reset our timer state to timerstate.reset and we also want to reset our elapsed time to zero milliseconds so we can start from the beginning again and this will of course also again trigger an update of our stopwatch text and it will change it back to yeah just a bunch of zeros and that is it for the most complex part of our video we can now dive into the UI and get to a little bit of whereas specific development here to to build the UI that displays the stopwatch in our main activity that first of all is nothing different since we just have a set content Block in which we can call any composable in here we want to get a reference to a review model which is equal to viewmodel we don't have the dependency for that yet let's include that by going to our Gradle scripts folder module app scrolling down to the dependencies and I want to paste these two lines on the one hand view model composed to get viewmodel references inside of a composable and runtime compose which gives us the lifecycle specific functions to safely collect flows in Jetpack compose let's click synchronize now close this and now we should be able to see this green view model function here we need to specify that we are looking for a stopwatch Remodel and there we go then we can get a reference to our timer State on the one hand by viewmodel dot timer State collect as state with lifecycle that's the one that comes from the runtime dependency so that the flow is not collected if the app is in the background and we need a stopwatch text which overview model also exposes so my view model stopwatch text collect s date with lifecycle and then I would say we start to implement our stopwatch specific composable um so let me relaunch this app very quickly so that will just be a column where we have a text for the stopwatch and then below this column we will have a row of two buttons and one at the play button and our reset button let's do this below here we can make that a private composable function called stopwatch this function will on the one hand need our timer state to know what icons to display for the buttons it will need the text for our stopwatch it will need a Lambda on toggle running when we simply click on the play button or the pause button it will need an on reset Lambda when we click on the reset button and an optional multiplier to change the appearance of this then as I said in here we're going to have a column for the column we're going to assign the modifier and just make sure to Center the content vertically Center as well as for horizontally like this the first element of our column will be our text that displays the stopwatch text so simply text here and we want to increase the size of that a little bit to let's say 20 SP I'll enter to import SP set the font way to semi Bold and the text aligned to Center then I'd like to add a little bit of spacing between the text and or buttons height ADP so ATP of spacing Alt Enter to import DP again and then we're going to have a row with our two buttons this will have a modifier of modifier filmex width and we're going to Center these buttons horizontally and then in here how do we achieve having these round buttons because on normal Android we have these yeah rather rectangular buttons from a material design but for where OS all these UI components have actually already been adjusted at least these material design specific ones so if we use a normal button composable right here this will result in such a circular button so when we click this button we simply want to toggle running we give it a text of actually no text we just want to make an icon here of course so I can the image vector or the icon for that will differ based on our timer state if our timer stayed for state is running we want to say the icon should be icons can we import that okay it seems like we need to add this as a material icons core dependency let's simply do that so Alt Enter add dependency Gradle will synchronize and yes now it knows that icons default pause seems like it doesn't know that maybe we need the extended icons so let's go to build a Gradle um and replace the core with expanded to get more icons synchronize this and we need to probably re-import that no let's try that again icons no let's take a look at the if the oh I wrote expanded it is actually extended of course let's resync and now this should work icons yes now we know this icon start default dot pause yes that works well now and else if we're not on the running State this is simply the play Arrow so icons default play a row and for the content description I'll just pass now here for the sake of simplicity let's copy this button and have one more for our reset button when we click this we say on reset here the icon is simply always constant so we can say icons default stop that is the reset icon and we need some more Arguments for this button because this should only be enabled if we can actually reset the timer so if it is not in the reset state so it should be enabled if the state is not reset timer status reset and I'd like to change the colors of this a little bit depending on its enable state but I think it does that automatically actually but I like to make that little bit less prominent than the other button because it look would look weird if both these buttons would be such a blue highlighted button then the user wouldn't really know where to click and which action is focused let's keep it a little bit grayed out we can achieve that with button defaults button colors and we assign a background color of the material theme colors background actually surface we should use the surface color for these little less prominent buttons as per the material design guidelines and now if we scroll up and call this composable here um stopwatch the state is our timer state like this let's put these on separate lines the text is our stopwatch text toggle running will be delegated to a review model toggle is running and reset as well like this let's pass in a modifier of modifier fill Max size and I would say we try this out this won't be final yet but we can see if the timer works at least and I am getting a duplicate class uh error here I already know why that happens if you get the same which you likely will then you need to Simply upgrade your carton version and compose a version so I build a greater project I will upgrade my compose version to 1.3.0 and the kotlin version to 1.8.10 and then in build.gradle app file we want to set the kotlin compiler extension version to 1.4.3 which works together with kotlin 1.8.10 click sync now and that should hopefully fix the duplicate class issue um IDE or a code oh no let's just launch this there we go it's working now um we're lacking a little bit of spacing between our buttons but let's try out if that works uh yeah that is looking like a stopwatch so let's pause this it's pausing it's resuming pausing very cool if we click reset our timer resets and we can start all over again that is looking quite good if it's reset we can click on reset but only on play yeah our stopwatch is working but as I said I want to show you a little bit more about Smartwatch specific development here let's first of all add a little bit of spacing between our buttons so going in here and say we have a spacer with a width of 8 DP then what I want to show you is as I said when we launch my already built app then we see that we have a little time text field here and that is actually rounded how can we achieve this rounded text well that is something you might want very often for Smartwatch apps and you can achieve this with a special scaffold for wear apps so that's just a normal scaffold as we know for real Android development which is just used to position a commonly needed views on the screen where these views these composables should actually be so in our case this commonly needed UI component would be our time View and we can do exactly that here so on the one hand you can see we can pass a vignette um a position indicator which we're not going to need here um that is if you if you have swipeable Pages then there's an indicator that shows where the user currently is on which page and a page indicator but we're going to use this time text so we're going to pass something to this time text and that is actually already a composable we can use here so time text and we can change how this looks like so time Source basically what it will use to display the time you can change the style so time text style would be time text defaults dot term text Style and you could change the colors for example for example to change the font size to something small like 10 SPF and you could change other things like the padding probably how it's curved things like that I've never changed that at all but you can pretty much change how this time takes looks like if we then take the stopwatch put it inside of the scaffold as a Content relaunch this then we should hopefully be able to see our time text and yes there it is and if we change the font size of this to let's say 20 SP then this is quite a large time tag so that's how you can manipulate this text uh something else you might commonly want in a smartwatch app is the so-called vignette oops vignette in here you can also use this custom vignette composable and say for example vignette position dot top or bottom or just both what this will do is that that won't be noticeable in our app right here even if we launch this you will see there is no difference but if you have scrollable content so let's say you can scroll a list or so a list of items then the vignette will will cause that there is a little black blurred Shadow here that looks like the user is actually scrolling below that shadow it's quite a cool look and if you have scrollable content that is something whereas apps commonly have but other than that I hope you like this tutorial of building a little simple stopwatch for SmartWatches that is something different and if you like this type of video then definitely let me know that down below and also if you're looking for more advanced Android premium courses you will find them in this video's description so if you're looking to become an industry ready Android developer check these out and other than that I wish you an amazing rest of your week and I will see you back in the next video bye bye foreign
Info
Channel: Philipp Lackner
Views: 25,692
Rating: undefined out of 5
Keywords:
Id: irIGZj1YON8
Channel Id: undefined
Length: 28min 47sec (1727 seconds)
Published: Sun Apr 09 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.