How to Build a Calculator with Jetpack Compose - Android Studio Tutorial

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey guys and welcome back to a new video in this video we will put a little calculator with jetpack compose so this is clearly targeted to those who just learned the basics of jetpack compose who know what a row is what a column is what a box is but who now want to take all these single concepts and put them together to a very simple app without any fancy like dependency injection architecture which might overwhelm a lot of beginners so let's just take the concepts you've learned for example in my jetpack compose basics playlist and apply this here into a very simple and an okay looking app actually which is simply a calculator so the way this will work uh will probably not surprise you we can enter some numbers here we can have done some kind of operation let's say a and we say okay minus 82 we press equals and then the result is -5 and we can also go on from here we can multiply this with 9 for example and we get the result as well we can delete some characters here to enter something else we can also do something and completely clear this screen and that is pretty much it and yeah of course we can also use decimal numbers so 5.5 for example times 8 and then we get the result so this will be a very simple version of the calculator what doesn't work here for example is we can't chain multiple of these operations that is actually a very brilliant homework for you after this so we can say eight times eight minus six then it actually just replaces the current operation here so yeah that is what we'll build i'll just jump into an empty compose project here which you can also just create you don't need any fancy dependencies actually just one for review model we um we will use a view model to just do all the calculation stuff but i will explain that once we get to that point and for that you just need to go to your build.gradle file and simply paste the viewmodel dependency for jetpack compose under the dependencies block like this so you can either just yeah i think writing it off is just the quickest here i'll also push the code to github so you can also copy and paste it there but it's really the only thing you need to paste into your project we can click synchronize now and then we will jump here into main activity to actually build the ui first i would say so that you can actually get a better impression of what epic compose is how it works how all these rows columns and stuff like that work together you can build response if you write with that so cool stuff first of all in an empty composed project we will see these two sample composables which we can delete and we can also delete the surface here which is initially created since we will build our very own ui here so the first thing when it comes to building uis with jetpack compose is that i take a look at the layout in the ui that i want to build which is in this case this one and i try to find specific components kind of of the ui that repeat itself and if we take a look at this calculator layout and i think it's pretty obvious that all these buttons here look fairly similar some some are wider than the others some have a different color but overall they are all very similar and if you have that in a project then you should make that a single component a single composable as we say in jetpack compose and that is what we will build we will build a calculator button so in our root package we will create another class or rather just plain kotlin file called calculator button select file here and we can write comp to create a composable function here which we'll also just call our calculator button and there we go and now the first thing when i build a composable is i try to think of parameters that we want to pass from the outside so in which ways can two different buttons differ so if we take a look at our layout again that could for example be the width it could be the color and maybe it could be the font so if you want to swap out the font or change the font size or so but that's pretty much it that is what we want to change you for these buttons so what do we need for that on the one hand we of course need a symbol which is what the button should represent so whether it's a number or it's the the plus operation or the minus one just what the the single symbol of the button should be then we want to pass a modifier which interpret compose is used to just do all kinds of common ui changes just changes that you would like to do to any kind of composable like changing the background color changing the the shape or so which you of course also want to do for this button so some buttons should be wider than others some buttons should actually have a different color so that is why we how we will use this modifier and something we also want to provide here is an on click lander because of course we want to be able to respond to click events outside of this calculator button composable when we click on that number then we want to send an event to our viewmodel that we actually just entered the specific number so the viewmodel can update our state but that's pretty much it i'll leave out the option to design the text here and i'll just hard code it into this composable if you want to be more flexible you could also pass a text style here but i'll leave this for this rather simple example here so how will we now create such a round button well in the end that's super simple we will just use a box for that a box and inside of that box we will put a text which is just centered in that box and then we can apply a modifier to that box to make it round and all that stuff so let's start with that having a box composable usually a box um is used to actually just arrange um the the child composables like the text for example inside of that box either in the top left corner the top right one in the center so that is one when we use a box and in this case here we want to just have one composable a child composer which is the text which we want to arrange in the center of this box so first of all i want to say okay the content alignment is actually center so all the content in that box should be centered in it we also want to pass a modifier where we pass the modifier we actually pass as a parameter and this one here then let's actually do it differently let's say we first create a normal modifier here and i'll explain why and then we want to say we clip this so that is how we get the rounded corners by simply clipping this to a circle shape and then we make it clickable since when we click on it we want to trigger our on click lambda and then we say dot then and pass our modifier the reason i want to do it this way around is let's say we would instead pass the modifier we pass as a parameter here then let's say we would want a different shape and then we want to say that then and pass our modifier so if we do it that way then our modifier that we pass just gets applied after all of these and if we do it this way around that we have the modify here then we will first take the modifier that's passed from the outside and then apply these things here so if you would then have a normal modifier and you would apply some kind of shape and do it this way around and you say clip then the clipping would kind of apply to the clipping you passed if we do it the other way around then we will apply our modifier at the very end and then we can open the black of code here and inside of that we will simply have one single text compo composable which will represent our symbol so a text which will be the symbol what else do we want to change i'd like to pass a font size of 36 sp as i said i will hard code this here if you want more flexibility pass this from the outside or the text style and pass it here for the style attribute and i want to pass a color of color white and if you work with like a light and dark theme here then you want to use material themes that colors instead so material theme there's one here that colors and refer to your maybe on background color or so but since we only have a cool dark theme here i will say color that white now what's next next is actually um we need to think about how we can how we could do this in a smart way that we sent kind of events to the viewmodel when we pressed one of these buttons and we minimize the amount of events we need so we don't want to have an event for each number we don't want to have like typed or seven event typed eight event typed five event um so we kind of want to have the minimum amount of events that we need to tell our view model what it should do if that makes sense so what we'll do is we will have one class of events for all the numbers which will just send a number to the viewmodel we will have an event to clear the whole the whole calculation state you can say we will have an event to delete the last character and we will have an event to perform an operation so that is one of these here one of the typical calculation operations and we will have one event for actually doing the calculation when we press equals and actually for the decimal number so when we click on the decimal place then we will also send an event so those are basically the events we need here and i want to put this in a sealed class in kotlin which i like to do called calculator actions actually here since all of these are kind of actions select tier class and then for each thing the user will do on the screen we will send such a calculator action to the viewmodel so the uv model easily knows what it should do with that specific user event so for example to actually send a number to the viewmodel we could have a data class number where we can pass a number of type integer of course and that is a calculator action let's actually just call this action in singular you can change the name pressing shift and f6 so what else do we have we have an object here that doesn't require any parameters which would be called clear to clear the whole calculation we would have an object to delete the last character we would have an object decimal to enter a decimal place we would have an object calculate to actually perform the calculation and then we would have a data class operation so when we either enter plus a minus and so on and here we actually want to pass that for which i like to create another class another cl class inside of our package called calculator operation select class and here that will be on the one hand to add two numbers calculator operation and we actually want to be able to pass a symbol that applies to this specific action so we can easily just show that on the button later on so here the symbol would be a plus to add two numbers we can actually duplicate this we will have a subtract which will be a minus of course we will have a multiply operation which i will use the x for i can use the asterisk i'll use that one and we will have a divide operation which i will use a backslash so it's actually forward slash um let's go back to calculator action now we can make use of this operation class to pass it here operation of type calculator operation and that's of course a calculator action so those are now the different things a user could do on our calculator screen and for each click event we will now attach such a calculator action and send it to the view model so in the view model we can then easily distinguish okay did the user actually type a number did they type an operation that they click on calculate that they click on like delete the last character so i think you get what i mean here and then the last bit we need here before we can jump into building the actual ui is we need a state so as you probably know in compose state is any type of value that can change over time and in our case well if we take a look at the app here and then what is a value on the screen that could change over time if we see okay we click something here well that is of course the number here and the way this will work under the hood is that we will have a number one we will have a number two and we will have an operation which all belong to the state so this would be the number one and as soon as we type an operation we will append this the state will now be so number one will be 854 the operation will be a plus and number two will still be empty because we haven't entered in number two yet but anything we enter from now on will be number two and whenever we then click on calculate the result will automatically become number one so we could start over again and we could enter a number two and so on i think that makes sense so here in our root package we will have a calculator state a data class which will simply summarize the number one which is i'll actually make this a string because it's much easier to deal with strings in this case and we will then later convert it to actual numbers to do the calculation and we'll have a number two which is also an empty string by default and we have an operation calculator operation which is not by default since we didn't type any yet then we will actually go to a root package we'll create a view model which we don't um fully right now but just to have some kind of source of state which i will call calculator view model so if you're not familiar with view models in jetpack compose the job of the viewmodel is in the end just to accept user events which are in this case as we as we mentioned here for example clicking on a number button clicking on an operation button and the the view model will then accept that and kind of map it to new state so the viewmodel is um responsible for actually changing the state so the ui can be updated with that new state and it will also keep the state since the view model is actually not destroyed on the screen rotation which is normally the case in android with activities so the state would be lost if you put it in an activity but it won't be lost if you put it in a viewmodel so far so good let's make that a class that inherits from viewmodel and then we'll just have a state here of our state by mutable state off and we need to press import twice here l plus enter and we create an empty calculator state here as an initial state and we say that is a private set that means we can change the state from the outside but we can still access and read it which is what we want we don't want to be able to change the state from our ui so now we can finally jump into main activity and start building the ui with what we have we can get a reference to our state and overview model here by using valve viewmodel is equal to viewmodel this composable function here where we need to specify what kind of viewmodel it is which is a calculatorviewmodel and we can get a reference to the state using viewmodel.state and i will make it a variable whatever our button spacing is and i will set that to 8 dp we can import dp here so the button spacing will actually be just the space between all of these buttons here so we can just have a variable to easily change that and to play around with it so the first thing we will do here in our layout is we will have a big box we could also put this in a separate calculator composable actually yeah let's do that why not let's do that in a separate file calculator which is a file we create composable call it calculator there we go and that will take a calculator state from the outside and let's also be able to pass a modifier here which is equal to the default one so if we take a look at our ui how will this work well in the end this whole layout will be our calculator here but it only actually occupies a height till around here so the calculator is actually only this part and this is pretty much free space so that is why i will put it in a box so we can simply align this whole calculator to the bottom so it doesn't stick around on the top here which would look ugly that is where i'll just put it in a box as a root layout and inside of that box we will then have a column so in the column we will have this main text here which will just be a simple text composable that displays the current calculation and below that we will have a lot of rows in that column so we will still have that one big column which also contains this calculation text but in that we will have one row here for this section we will have another row here basically five extra rows here for all these buttons and that is how we can arrange this in such a grid and i will show you how we can also do that in a responsive way so that you have the same space here that you have some buttons with a wider width and yeah that's pretty much it so let's go back here and start with our box composable we will apply our modifier and we can actually open the block of code here in which we will create our column here the modifier will be modifier fill max width so we just fill the whole width of the screen and we say we want to align this in the center actually bottom center and that is only possible because this column is inside of a box that is why we have access to this align modifier what we also want to do is we want to say vertical arrangement is and you can see we have a bunch of options here to choose from um a space between center bottom which you've probably learned about that is just yeah basically ways how we can space items in a column but something that i will use here is arrangement dot spaced by and we pass our button spacing that we actually need to pass here which is a dp unit and let's also default this to atp and then we pass our button spacing in here and what that will do is it will just make sure that there is adp of space between all items in that column in a vertical direction then we will have a simple text in here which will represent the calculation and the result so what will the text be well we can actually construct the text out of our state so we can say it starts with state at number one which is a string again so we can easily do this we want to add the operation if there is one so let's open parentheses here say state at operation that comes next and if that's null we just don't want to append anything just an empty string and after that we want to append state number two so that is how we in the end get our whole line of numbers and operations want to make sure that it's aligned towards the end so it's just right aligned we apply a modifier to make it fill the whole width again and we give it some vertical padding of 32 dp this vertical padding will make sure that we have a bit of space here and a bit of space here i like to give it a font style of is it no it's actually font weight font weight of light oops font weight that light a font size of 80sp so quite big import sp give it a color of color.white and a max lines of two so that it never occupies more than two lines so next we actually only need to worry about our buttons which are quite a lot and that will be that would be quite a lot of code here um usually if you have such repetitive patterns here like a lot of buttons you could put these in a list and just describe the properties of each button so like the symbol you could define the color and then you could just loop over this list and add all these composables but in this case i found that this is not a too good approach at least for this layout to make it a good approach it would require a lot of more work because it's quite hard to describe in a list that some buttons are actually wider than others so how much space some buttons occupy and that others occupy less space of the grid and that is why i just put in all the code here since it's unlikely that it will get a lot more here so we'll start with the first row which we'll have a modifier of modifier fill max with again and this time we say horizontal arrangement is again arrangement spaced by our button spacing so we just also have the same space horizontally and in here now that will be our first row which will represent these three buttons the ac button the deletion button and the division button so we will have our calculator button in here the symbol for this one will be ac and we actually also want to pass a color here for all these buttons so they are not white or so um let's do that with a modifier modifier dot background would be in this case they are a light gray so let's just define some basic colors which i will paste in the color file here in ui theme just below this here three colors medium gray light gray and orange these are the three colors we use yeah feel free to copy these from my github or just type this off i will use light gray here not colored light gray we just want to use our own light gray and something we want to make sure for this button is that it actually is twice as wide as a normal button and for that what i like to use is on the one hand aspect ratio an aspect ratio of two so that the width of the button is twice as wide as it is high and we also want to apply a weight which we can do in rows and columns so with the weight we can say okay that actually applies um that has a weight of two and if we then give the other two buttons that we have in that row a weight of one for each then compose will kind of figure out that this button here has a higher weight than these other two buttons so it will occupy a larger um proportion of the width of the row and in here when we click on this i actually don't like this one let's just put it as on click in here we say something like on action and the action that we want to pass here is calculator action dot clear since when we click on ac we want to clear and we of course need to make this a lambda we pass here let's also push the modifier up here say on action and here we pass that calculator action and we hoist it up to our main activity later on which can then call the viewmodel with that action cool now all we will do is a bunch of copy and pasting and changing some properties so let's copy the calculator button paste it two more times for this row this one here will be the delete button background will also be like gray the aspect ratio will stay one here and the weight and we call the delete event and this one will be the divide one it will actually be orange this one and also a weight of one f and an aspect ratio of one f we call this time an operation and the operation will be divide that is how we can do this then let's actually copy the whole row and paste it we will now do this for the other rows as well and again as you see that is quite a lot of repetitive code here which you which i would optimize for a larger project in which i know that the grid for the calculator must be very flexible um but i think it is it doesn't need to be very flexible here um which is why i do the simple approach also just for the tutorial of course um but it would be quite yeah it's just not so easy to make it as flexible that you can just define which button actually ranges how wide so um that that you manipulate the weights and here just with elements in the list if that makes sense so in the end what you would want to build is something similar to the css grid if you're a little bit familiar with that which is not too easy but never mind let's get back here into our second row which will start um with number seven and that is just a weight and aspect ratio of one so it's just a squared one or rather circle one since we crop it uh light gray is not correct here we want to use color.dark gray here and the action will be a number and which number do we type well d7 we can actually copy this here for the rest so one two three so we have four items in that row this will be number eight number eight here we have number nine and number nine and this one will be the multiply operator color orange for this one actually just orange and here we don't call number we call operation multiply again we copy this row paste it here for this one we will actually start with four here as well four five and six and the operation in this case will be minus this one will be orange that's fine here we replace this with a subtract and one more time actually two more times this one starts with one dark gray is fine here we have one we have two [Music] this one is three oops and the last one the orange one again is plus this time so we say operation dot actually add i called it and one last time here the first one is actually zero pass zero here for this one since it occupies two columns again we will make sure that it has a weight and aspect ratio of two this one will be the decimal place so just a dot um instead of number we want to send the action decimal and this one the last one will be equals which will also be orange and we send the action oops calculate and the last one isn't needed since we only have three buttons in the last row but that's pretty much how we built the ui here as i said quite a lot of code but since we could copy paste a lot it's not so bad let's go to main activity and have our calculator here the state will be our state and the action will be actually view model double colon on action which is a function we don't have yet and we want to pass our button spacing button spacing is button spacing and actually also modifier the modifier will just be a modifier filling the whole screen it will have a background color of color dark gray and i think i actually made a mistake with the colors i actually wanted the dark gray one as the background color as you see um this one is oh no the the background color is a medium gray this one is darker so i i did it right but this one actually needs to be medium gray which i passed in the color file and i just want to apply some padding of 160p here cool that is it for our ui the remaining remaining part of the video will be to have the business logic in the viewmodel so what we do and how we map the state so that our calculator actually works so let's go into the view model and create our function on action and this will take such an effect as such an action which is a calculator action and here we can simply distinguish in one expression if that action is a number from our calculator action then we want to say enter number which is a function we'll create and actually also pass the number here from the action if that is let's say the action decimal we say enter decimal doesn't need a parameter this one is clear so in this case we can directly say the state is just the initial state again so we just clear our whole state if it is calculator action dot operation we say enter operation action.operation if it is calculator action dot i think that's it right um operation decimal number calculate that's missing of course then we say perform calculation and it's actually not too much work here um to do that what is missing here other delete branch is missing okay let's also say is delete so we say um just delete um well let's let's say perform deletion cool now we can go to these functions press alt enter and press create function enter number that's fine we do it for the enter decimal just to create all these functions in a quick way enter operation perform oops perform calculation and perform deletion cool let's start with enter operation because that's quite a simple one well now we actually need to take our state and decide depending on the existing state whether we want to be able to enter an operation or not so if we take a look in our app then now since we entered the first number we actually want to be able to enter an operation like multiplication here and then we can enter a second number but let's say we have an initial state with no number entered then we don't anything to happen when we click here on an operation so that is what we want to check if state operation is actually not state operation if state number one is not blank so if we entered something for number one we want to apply the operation to our state so we say state is stated copy and then the operation is the new operation if you're new to what stated copy means that basically just creates a copy of our state changes the operation the rest remains the same and then it applies this whole new state with the changed operation to our existing one without actually needing to make our single fields of the state mutable which would not trigger a recomposition here cool let's next go to let's say perform deletion what do we want to do here we want to have a one expression because depending on what we entered we want to delete something else first of all we want to check if the state number two is not blank so if we entered something for the second number then the first thing we want to do is we want to remove a character from this from the second number so then state is stated copy again and here we just want to change the second number so we say state at number two drop last one so we just drop the last character here of this number and assign a new result to the new number and the new state next we want to check so this would now be executed if the number 2 is blank so if we did not enter a number 2 then we want to check if we actually entered an operation because that would be the next thing we would want to delete so if state operation is not now status stated copy and operation is now in that case since we delete that and finally i guess you guessed it state number one is not blank then we say state is stated copy number one is state number one drop last one and that is our delete function and let's next go to enter decimal when do we want to be able to enter a decimal well either if we are currently entering the number 1 or if we are currently entering the number 2 and then we also want to make sure that the number 2 and number 1 actually already contains some numbers since we don't want to be able to enter the decimal as the first character of the number string and we want to be able to make sure that the number one and number two both actually don't already contain a decimal place because we of course only can have one in that string so if the operation of our state is null and our state number one does not contain the decimal place and state number one is not blank then we want to apply the decimal place to the first number let me reformat that a little bit so if we did not enter an operation yet we know we're currently entering the first number if the first number does not contain a decimal place already then we know we could enter one theoretically and we want to check if the first number is now blank so we can't enter the decimal place as the very first character so if all this is not the case or is the case rather you want to say state a state copy and number one is state number one plus or decimal place and we can return out of this if condition now if we did not return here so if this condition was false we want to check if we could if we can enter such a decimal place for the number two and that will essentially be very similar so we can copy this if condition we don't need to check for the state operation being equal to null here because that is not null if we can enter the second number we need to swap this out to number two this one as well and this one as well and the return is not needed so that is how we can enter a decimal here then we still have the function enter a number so how do we check if we want to add this number that we entered how if we want to add that to the first number the second one well we can again use the operation for that state operation is equal to null because if that if we did not enter an operation yet we know we want to attach this number to the number one and i also want to kind of add a check here that the number can't get too long so if the um state number one dot length is longer than or equal than like a max num length which we can create here we simply return so let's just have a companion object with a private cons val max num length which i'll set to 8. so if the number is not too long already we want to simply add it so state stated copy number one is state number one plus our number we entered then we can return here we can again add a check here so if this is if we go on here we know we need to enter number two we need to add it to number two if state number two length is longer than the maximum length we can return and else we say state is stated copy number two is state number two um plus number and that's it for the enter number function and one function is missing which is our perform calculation the most interesting one of course first of all we want to cast our actual numbers now so since we still save these as strings in our state we want to have these as actual double values so we can also calculate with decimals so vowel number one is state number one two double or null it will be now if we can't cast this which should not happen here to be honest number two as well and number two and we will have actually a result which we can calculate based on the operation so when state dot operation is at we want to say the result is plus number two we want to say if it is subtract number one minus number two if it is multiply number one times number two or if it is divide then we have number one divided by number two and of course we get a bunch of issues here because these are null values or nullable values so we first of all need to check if number one isn't now and number two also isn't now then we can kind of perform the calculation here and it could theoretically also be now like the operation that we simply want to return here which shouldn't happen at this case or like it it can happen actually i don't think so that it can happen because we would already have returned here if one of these numbers would be no and if both numbers aren't null then we can calculate something so after that after we've calculated that we can say the state is stated copy and now the number one will become the result so number one is result dot to string since we save that as a string then we can say take 15 which i just like to do to not make these numbers too long so we'll just take the first 15 characters here of the result we also want to make sure that number two gets reset to nothing so we can enter it again and that the operation gets reset back to now and that's it for calculator i think so at least so in our main activity we already have the view model so that should already work let's just launch this here on my device and see if everything build was successful that looks good let's wait until it launches and there we go looks pretty similar let's try to enter something with eight five um okay what happened now um looks like somewhere i did something wrong okay i clicked on multiply and it prints my package name i guess it prints some kind of action as a string so calculator operation let's check our operation function here oh i see what is happening it is actually in our calculator here that's the issue because i print the operation and not the symbol of the operation that is what i want to do the symbol would be the actual string representation so let's relaunch this here and then that should be fixed five times five yeah that now looks good equal 25 decimal places also work cool we can delete characters it's a little bit weird that it goes from the left to the right here because of our right align but it's fine i guess we can clear this thing we can use decimal places looks pretty good looks like a working version of a calculator so i think such a simple tutorial is just a lot better to get started with a technology like jetpack compose without just needing to deal with all that complex stuff like mvvm dependency injection retrofit dagger and all that stuff beginners are quickly overwhelmed with i think this is a very good way to just build something very simple if you like this and if you want me to do more of these simple apps like i could also think of something like a tic-tac-toe game or so then let me know that down below and i will see what i can do if you want to take this to the next level however then you can watch my stock market app tutorial following clean architecture and all these fancy guidelines if you want to see and learn that then simply click here
Info
Channel: Philipp Lackner
Views: 69,009
Rating: undefined out of 5
Keywords: android, tutorial, philip, philipp, filipp, filip, fillip, fillipp, phillipp, phillip, lackener, leckener, leckner, lackner, kotlin, mobile, compose, jetpack, calculator, ui, ux
Id: -aTcFJWxEQA
Channel Id: undefined
Length: 43min 24sec (2604 seconds)
Published: Sun May 22 2022
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.