How to Make a Chat App With Ktor - Building the Server - Part 1

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey guys welcome back to new video in this video or rather in this new playlist which will be a three video playlist we will actually build a very cool chat app using ktor including mongodb server side client side we will make a simple android app that's not a surprise here with jetpack compose and sticking to the clean architecture guidelines and that's what we're going to do in the next three videos including this one so first video we'll be making the server which will be this one second video will be making the app which i will show you in a moment and the third video will be where i show you how you can actually deploy that to your very own server so you can use the chat with your friends so let's take a quick look here in the app and how that will look like in the end you can see we just have a very basic text field where we can enter a username that will just be one single public chat room where everybody can join who has the app so let's actually take a look here we can join with philip say join chat right now there are no messages we can join with another account like peter here for example click join chat there we go and now we're actually already ready to chat so we can say hi youtube and when we then click send you can see we actually send the message it arrived in real time here on peter's account we can reply here with hi philip what's up something like that i don't know send that and you can see it it's basically instantly there and that is what we will do in the next three videos and i think this is a very exciting topic a lot of people ask me to make more what k tour mongodb just having some kind of own backend that you use with your android app this will use web sockets so you will even get to know a little bit of that and that will be very exciting and at this point i want to thank my sponsor hostinger who will provide the server we will actually deploy this chat application too they currently have a huge black friday offer here on their website you will find the link down below houstinger.com so in case you actually also want to deploy your app and you want to use that with your friends which totally makes sense then you can you can find their different plans here on their website and once you decide for a plan you can see they they really offer a lot like up to a hundred websites lots of storage once you decide for that you can simply click select here then you will get to this order page you can pick your billing period that you prefer enter your details payment methods and what you don't want to forget here is to actually click on have a coupon code because you do have a coupon code if you're watching this video you can simply use the code phillips7 and then you will get additional seven percent on top of that even very cheap price which is just two dollars a month for a server you can just deploy your backend to and that's pretty cool once you're done logged in it will look somehow like this so this is basically your web interface in which you can manage all your servers they also have a very great web hosting plan share web hosting if you decide to have a website then i can only recommend having that as uh here on hostinger you can have a domain you can see i registered a domain here which is currently very empty and i have a vps server that is what we are going to use to deploy our chat here too vps stands for virtual private server it's in the end just a virtual machine a remote linux machine where we have root access to so we can install all the stuff we need on that server like mongodb for example and then simply use that to yeah chat with our friends together so that's pretty cool and i'm looking forward to that okay there are two things you actually need to do before starting with this course on the one hand you need to create a k-tour project so in case you've never done that then you need to go to start.cater.io and here you will actually find that project generator there is a plugin that is available for intellij which is the ide that we use to make that cater server so you also need to download that in case you don't have it however that plugin is only available for the ultimate version of that which is paid and for those who don't have that you can use that creator project generator website here it's free you can basically just configure your project here and then simply download the initial project as a zip file for this project if you don't want to do this you will also find the github link in this video description where you will find the initial project setup that you can just pull into your intellij ide so here on this website you can see you can choose a product name i will just choose ktor chat we can open this adjust project settings block here you will see you can enter a website which will in the end just make up your package name the artifact yeah it will pretty much be the same also some kind of package name you can choose a catered version i will just use the latest stable one 1.6.5 the engine is fine leaving that at nutty and the configuration file will be a hokum file so that's just yeah it's similar to json in which we will basically just put our server configuration we can leave this checkbox checked here to add some sample code because before we actually generate this we want to click add plugins and let's think about what kind of functionality we actually need in our server to make our app on the one hand we need sessions so we want to store a specific session by a user when they are actually connected to the chat you will see later how this works it automatically ended this routing block as well which we need because we want to be able to have some rows at which we respond with content then let's see we have content negotiation i'm not sure right now if we need this i will add it this is basically for json parsing so if we want to automatically parse some data to and from json to send it over the network i actually don't think we need that i mean we need serialization but we don't need that automatic serialization so let's actually remove this we will see later in the worst case we just have to add another dependency we can add call logging that's just yeah for convenience then we'll see if a client makes a request and let's add kotlin actualization for serializing some data as i said we will do that manually here and then only websockets is missing so web sockets in case you don't know that that's just for real-time communication so we just establish a fixed connection between two clients and yeah they can basically send data to each other at any time so that's pretty cool we're done here we can click generate project and then you can see it will download that as a zip file you simply put that in your intellij project folder extract that and then you can open it intellij so i will see you back in intellij so there we go that is our empty cater chat project here let's quickly walk through this so we can actually understand this and just see how the the typical caterer structure looks like so it's quite similar to android actually a little bit different but yeah you also have that source package with your source code you have a main package and test package we're not going to write any test cases here so let's open the main package kotlin and here you will find a plugins package and an application class if we take a look in the application class that's basically the entry point for our server you can see here is the main function and it will just call that engine main dot main which will then trigger this module so you can use k2 with multiple modules that you define in your code but we are going to use the single one here which is totally sufficient let's take a look here you actually have a lot of these configure blocks so these configure blocks configure so-called plugins you've seen these when we actually created the project which were yeah these options that we actually checked like web sockets like uh serialization here in cater these are called features so single feature is just a single thing your server can do like serializing data like web sockets like routing that would be a feature and with these configuration blocks you can actually just define how you would how you would like this feature to work the actual configuration you will find in the plugins package so let's take a look at routing for example and you can see it just inserted some sample code how routing would work with ktor we can remove that here because we really don't need this if we take a look in serialization for example you see we can just install this content negotiation feature and basically just set up json here so that will set up uh kotlin serialization and you can see when we then would respond with such a map for example it would automatically parse that to adjacent string and respond with that at that specific road however we also don't need this so we can also remove it and if we actually take a look here at the left then you will also find a resources folder if we open that up you will find two files only one of that is interesting for us which is the application config file if we open that you can see that's just the file in which your server configuration belongs this is this hoka file i talked about here you can define your port where the server is running on your modules and a lot more like if you have some secrets like uh jwt authentication then this would be the place where you put this or if you have an ssl certificate then you can configure that here in your config file and if we take a look at the root level of our project then this really looks similar to android we have our build.gradle file which is the carton version of gradle here which i actually like more if you open that up you will just find your dependencies that are already included here i will include some more which i will explain what these are for just pasting these on the one hand we have kmongo okay is a cotton library that is able to access your local mongodb that is running on your machine i will show you in a moment how this works so just make sure to add these two dependencies and then also add these three dependencies here which are for coin so we will use coin here for dependency injection on server side we don't have stuff like dagger hild so here we will use coin and you can see we still need to define these versions which we can do in this gradle file up here we can just duplicate the last line twice here have our k version and our coin version we can then define these versions in our gradle properties file on the left side here on the one hand we have our coin version which is um it is 3.1.2 and we have our k version which is 4.3.0 if you've done that and made sure that there are no more errors in your gradle file you can click this little sync icon here to synchronize your gradle and yeah just to make sure that the changes are applied well right now we wouldn't be able to use mongodb because mongodb is actually a program that needs to run on your computer on your local machine because of course if you want to save some data in that you actually need some kind of program to talk to send commands to and if you don't have mongodb so if you have that you can skip this section but if you don't have that which is pretty likely if you never worked with backhands before you actually need to install that and for that i have this link open here you can find this also down in this video's description it's a mongodb.com slash try slash download community it's a free community edition here you can do whatever you like with that so make sure to open this community server choose your version preferably the one that i use here choose your platform your package for windows i will use msi and click download and a few seconds after that once that is downloaded you can actually click that installer and install mongodb so that's what we're going to do here let's click that and this wizard will open up here where we can then click next we want to accept this license agreement next you can just click complete install here we want to leave everything as it is basically i won't install that in my machine here because i already have it so you just want to make sure install it as a service run service as network service user clicking next and you also want to make sure that this checkbox is checked to install mongodb compass that is actually optional but it really helps with development because that's basically the uh not really graphical version of that but yeah it is a windows program in which you can just click through your collections and you just see what's in your collections so it's it really helps debugging it really helps changing a few fields without actually needing to learn on the command line so once that is done i one click next here we will go back to intellij and actually now start to work on our project i will close these windows here and we will actually start on our data layer so we will start to write the mongodb stuff in our root package i will create a new package called data and in the data package we will have a model package it's a very very simple database here because in the end we just save our messages that we send into our chat to be able to load these initially when we join a room so in our model package we're just going to have a single model and that will just be called message i will select data class here and let's think about what kind of information we actually need to save with a specific message so on the one hand that is of course the text the message actually reflects so obviously every message has a message surprise we will also need the username of the user who sent the message and we will need a timestamp i belong so we actually know when the message was sent because we saved this message in our database we also need a unique id for that so we can actually uniquely identify each message and also find that easily with our queries we can simply define this id with a val id will be a string and we default this to object id this one from org dot b's and dot bsn.types create a new object id and say that tostring so that will just generate a random id that we assign to this message and because this is an id we need to annotate this with at bsn id we want to import that and there we go one more annotation we actually need is the serializable annotation which just sets this class up to be able to be serialized with json because later on when we implement our websocket stuff then we will just take such a message um serialize it so we have that as a json string and then we send that json string over the network in real time to the other app so now that we have that message entity we want to actually implement our data source so we just want to have an interface that defines the functions we actually need to access our database in our case those are just two functions on the one hand to get all messages and on the other hand to actually insert a message into our database so in data we create a new carton class of file called message data source like interface and as i said these will just be suspend functions of course asynchronous here because of io operations writing to a database and stuff like that and reading from it this will be get all messages and we'll return a list of messages from our model package and we're going to have another suspend function insert message where we just pass a new message and insert that in our database now we need an implementation of this data source of course to actually use that in combination with mongodb because i'm actually doing this interface here to to have that as an abstraction because then we can actually pass that to wherever we like without actually specifying hey this is specifically for mongodb because if at some point we decide to have some other kind of db then we don't need to change this interface we only need to change the underlying implementation which just allows us to not need to change a lot of stuff in our code so in our data package we create a new carton class again this will be message data source implementation and it will implement that message data source interface we can press ctrl i and implement all these functions and this will take a constructor argument here which is our actual database instant and instance and that will be creatine database cool routine database which is an instance you can see of okay curoutines and it's just a database instance yeah that uses curtains to write and read from our database and to actually now get access to our message collection so in mongodb we just save everything in collections of documents it's just like a firestore if you know that or real-time database just json blocks of data in the end key value pairs and if we don't have such a collection yet okay mongol will automatically create that for us it's really easy we can just define our messages collection here actually make that private using db get collection and passing our type message it's really that easy that's all we have to do to create that message collection it will automatically know how a message looked like and if it does not exist then it will create that the first time we actually insert a message the next step is actually to implement these two functions which is as easy as actually creating the collection here for getting all messages we can just return messagescollection.find we don't need to pass any arguments for that find function if we want to get all messages which we want and we can say that the sending sort we just want to sort this descending by a date so we just pass our timestamp and here in k-mongo we can simply do that using the actual entity class double colon specifying the field we want to sort by which is the timestamp and then we can say to list and it's it's really really that easy to get all messages from our database in okay and to enter the message that's even easier we can just say messages insert one we just want to insert a single message and that is the one we passed as a parameter okay what is the next step here in our project the next step is that i actually want to write a so-called room controller which is yeah the control of our room it will manage the chat sessions so we only have a single chat room and the room controller will just make sure that it keeps all the references to all the members that are currently joined in that room including their specific websocket sessions which we can then use to send messages to these users let's go to a root package create a new package called room and in this room package we will create a carton class called a member so a member will just be a single user who is currently in our chat room and this member will need three fields here on the one hand the username then it will need a session id also tab string so session id is just a unique identifier for a specific session so as long as that member is connected and it will need what else will it need it will need a websocket which is a websocket session so the socket is what we then use to actually send something to that member and in the android app we will then receive that message and display it correspondingly in our chat list and one more very very small class that i want to define before jumping into the chat controller or the room control i call it is just a custom exception so in our room package new kotlin class and that will be member already access exception so if we actually want to join a room and there is already a member with our username in that room we just throw this exception and display corresponding error in the app so we can simply let that inherit from exception and for the text here let's put that in another line basically just say there is already a member with that username in the room something like that and then we can go to room package create a new cotton class and that will now be our room controller just like this this will actually need our message data source now make sure to pass that interface because in this room controller when we send a message we of course want to also insert that in our database the first thing i want to define here in this class is a private val members so we are actually going to use a so-called concurrent hashmap to save our our members that are currently connected to our chat so what is a concurrent hashmap i hope you know what a hashmap is it's basically a data structure that saves key value pairs and it's very efficient to actually read these key value pairs and the thing is server side we often have a lot of multi-threading going on so a lot of requests going in at the same time a lot of database operations are currently happening and if we then use data structures and we don't make sure that we actually deal with this concurrency then we can face a lot of so-called race conditions i won't go deep into that here but this concurrent hashmap actually knows how to deal with that and make sure that it will that only one thread at a time is actually writing something to that concurrent hashmap so the keys will be strings which are the usernames which are unique and the values will be our members and here we can just create an empty of this empty version of this hash map and let's actually next think about the functions we need in our room controller on the one hand we want to have a function on join so when a new member actually joins our room this will take the username of that member it will actually take the session or actually not the session instead just the session id just everything we need to create such a member and also the socket which is a web socket session so in here let's think about what we do when a member actually joins the room when they do that we want to check if there is already a member with that username in our hashmap if so we will simply throw the exception we just created so if members contains key username we want to say throw member already exists exception and if that's not the case we say members at the key of username is equal to a new member and we can say okay username is username session id is session id and the socket is the socket just like that so what else do we need we also of course need a function to actually send a message so when a specific user sends a message we want to broadcast that to all members in this room so parts are good let's write that function send message here we need to know who actually sent that message because we will attach the sender username to that message body so everybody can actually properly display who sent that and on the other hand we of course want to know the message we will actually want to send and that's just a string here in this function we will then create our message model for our database so we can say um actually not a val we first want to take our members list our members hashmap and say for each the yeah for each value that's fine and it gives us an error um or let's do members that values that for each yeah that works so here for every single member in our members hash map what are we going to do first we actually want to create an entity for our database so message entity is equal to message the text will be our message the username will be our sender username and the timestamp will be system current.millis and the id will automatically be created so far so good let's actually use our message data source and insert that message into our database so message entity since that suspend function we need to execute that in a curtain here i'm going to just make that suspend function as well so that error goes away but we're not done yet in this function right now we just save our message in the database we also want to parse that message to a json string and then send that json string to all members in that room because we can't just of course send a message object via web sockets we can only send strings or or just bytes in the end in the end the string is just a set of bytes so we will have a parsed message here using column serialization which we can then simply say json i think this one dot encode to string and i think this is actually the wrong function we should just be able to pass our message entity and yeah you can see we can import the correct one pressing alt plus enter there we go and then we can say member so the current member we're yeah we're referencing here in this loop we take the socket of that member and we send a frame so a frame is just something that can contain data is it's basically something we can send via web sockets there are different types of frames we want to send a frame that text because we just want to send a string here you can also send byte frames if you want to send some file data or so i don't know and here we just say first message and that's now it that's how we basically send a message using web sockets over the network then two more smaller functions are missing for this class on the one hand that is just a function to be able to access all messages and to read these from the database that's a very quick function just suspend function here get all messages returns list of messages now we can just say return message data source get all messages and finally if a member decides to disconnect if they leave the app or so we also want to have a disconnect function so we say suspend function actually i will call it try disconnect because if the member is not in the room here there is nobody to disconnect then in here what we will do we will say members actually want to pass the username here who we want to disconnect members using the key username that socket that close so we just want to close the socket connection to that specific member and then if there is a key in our members list called username we actually want to say members dot remove username so we just remove that member from our hashmap and that's already it for this room controller class the next step is to actually set up our sessions because every time we connect to the server here we want to set up a session and connect that or to associate that with a specific member who joined and for that i'm going to go to our plugins package create a new carton file called sessions just a plain file and here we'll extend the application class with another configure block and call it configure sessions and nevertheless i put a hashtag in there we'll quickly remove that so what will we actually put in here we'll first of all install the sessions feature i'm actually not sure if we have that somewhere else here security oh here we already have that so then let's actually remove the sessions file again sorry and configure that here and configure security removing the routing block and [Music] yeah that's basically how we do that we define a data class that contains our session data i will actually not do that in this function instead i will create my own data class for that questions where i will create a sessions package in our root package and in that we will have our chat session simply create a data class that will contain the username of that user and it will contain a session id back in security we can install the sessions feature we want to have a cookie we can remove this stuff we'll just call it session and replace my session with chat session then another thing we want to do here is we want to call intercept application call pipeline dot features and this is now the place where we will actually assign the session so this will now take a look at each and every request client makes to our server and takes a look okay is there session data attached and if so it will assign a corresponding session so we can check if call.sessions.get passing in our chat station type so it knows which session we're talking about if that's not so if there is no session for this particular user we want to say call dot sessions that's set and we say a new chat session the username will be actually something we can get here as a query parameter that's how we will attach that so val username is call in this case so call just refers to the current call by the client and contains information about that just like parameters for example here we will have the username parameter and let's say if for some reason that's not provided we just assign the name guest then we can say username and session actually we have to generate a session id here we can simply do that with generate nuns so that's what that will just generate a random string here okay we're already pretty far here in our server what's now missing is the actual route at which we connect with web sockets at which we receive these requests from our clients and then you properly use our room controller to actually handle that and we need our codeine setup so we have dependency injection and stuff like that which is very simple so let's actually start with the more complex part which is the route actually two routes on the one hand we have a route to get all messages on the other hand to connect to our chat so in our root package we will have another package called routes and in that we're going to have a file called chat routes select file and this will also just be functions in ktor extending the route class and the first one will be the chat socket route this will need an instance of our room controller which we'll make use of here so first of all we want to specify that we actually want to be able to receive a websocket connection here at that specific route so we say web socket and here we can also define a path at which we want to connect here so basically just the route which will be slash chat dash socket so that's just the url we will connect to and the first thing we want to do is when i get the current session by this user so we can say val session is called.sessions.get that will be of type chat session and with that session we just now get information about that specific user for example like the username or just the session id if this session is actually now which shouldn't happen oops if session is null come on then we actually directly want to close this websocket connection here because then we don't want to connect to the chat if we don't have a session so we say close and we can pass a close reason so the client would know why that's closed or i don't know what's the exact um the exact use case for that close reason what we can provide one here close reason close reason codes a dot violated policy we can attach a message which would be no session and we of course want to return out of this websocket block if we however have a session and we arrived at this point in our code we can say room controller on join and actually join our chat the username will be session username session id will be session.sessionid and the socket will just be this so actually this that no yeah just this so this will be the default web socket service session which we get here in this block so this will now be called every single time a client connects to this route via web sockets so then we actually get the corresponding sucked connection we can then attach to the member to use to send something to that specific member because this could potentially throw an exception here i remember already exists exception let's put this in a try and catch block so if there is a member already exists exception we just want to handle that in that case we would just say call that respond with the conflict with the status quo conflict so we can pass on flick here and yeah we could also attach a message we probably don't need to we can just treat this the same way as we yeah as we just do it here so if we receive a conflict code client side then we just say okay the member already exists we also want to be able to catch other exceptions because when the socket connection is closed it will actually throw an exception so in that case we could simply print that so either print stack trace and one more thing actually in this trying cash block is a final block so what we actually want to do in each and every case no matter if this succeeded or failed after we actually went through this try block or these catch blocks we just want to make sure that the socket is closed properly so that we properly disconnect from the chat so we say room controller try disconnect oops try disconnect passing our session username because we're not done yet after we actually joined the room we want to say incoming so you can see that is now a received channel we can say incoming consume each and here we get the frames so this piece of code is now called every single time there is a message sent to this websocket so yeah i know this is a little bit confusing right now so we basically have this web circuit connection this websocket server session here which is the socket connection to a specific member and if this member would receive a message so is there if there is a message directed to this member this block of code will fire off with the corresponding frame and that frame usually in our app will just be a text frame so if frame is frame.text then we say our room controller send message the sender username is just session.username and the message we want to send is actually a frame that read text so that's how we decode a frame and actually get the string as text so just for every single incoming message we or rather for every year for every single message that comes from this particular session we just broadcast that to the whole room if that makes any sense then one more route as i said that we need here is actually to get all messages that's a very simple route get all messages it will need our room controller and here we just have a get request we can simply say get define the route at which we want to access that which would just be messages here and we can say call that respond import call we want to respond with http ok i'm going to say room controller get all messages and we can even put this in separate lines that's really everything here already there isn't much that can go wrong when we access this route we just return all messages from our database and yeah functionality wise that is already it as i said we still need to set up coin here for dependency injection which is very simple so i actually just want to create another package in our root package called di for dependency injection and in there we will create a class called a main module actually not a class just a plain carton file if you know dagger hilton android you will know what modules are i won't deep dive here into dependency injection we will just define that main module here that's how we do that with coin we have a new we have this module block and here we can define the dependency that we actually provide on the one hand that is our database instance so we can say single to define a singleton and we say okay actually this one here from reactive streams dot create client dot co routine this will create such a curing client and then we can say get database to get the security database i talked about when we provide a name so we can say message db i will actually attach youtube here because i already have a message db so that's how we provide our database instance then we need to provide our message data source so another single message data source and here we say we return message data source implementation and now how do we get our current database in here well because cody knows how to create that since we defined that here we can simply say get just like that and it will automatically insert that then what else do we need just the room controller that's pretty much it afterwards so we can create that here and since we also know how to create that message data source we can again just say get so far so good let's have a plug in here to configure code actually let's not do that let's just directly do that here in our application class at the very start we say install coin and we can configure that here all we really want to do is we want to say modules we just want to define the modules we have here and we just pass our main module and that's pretty much it so if we now want to run the server you can simply click this play symbol next to main and click run application kt let's hope everything goes well if not we will fix things but i think this should actually be it you can see for me it will probably crash right now for you it does not have to you can see because address already in use that's the issue if you are actually launching a server on a port that's already running so where there is already an application on your pc taking that port which is the case for me doesn't need to be the case for you if it works for you that's totally fine then you don't need to change anything i do need to change something so i need to go to application config and actually change this default port to something that's free on my machine i typically just use yeah we can just do eight zero eight two or so relaunch this and then this should not crash and no it doesn't so how can we actually now test this because we obviously don't have the app yet well there's a cool website where we can actually do that which is actually this website here so it's pisocker.com websocket dash tester so this web circuit this website we can actually use to connect to our websocket server and send messages and then we will see how the server responds so here for the url i will pick ws which is the websocket protocol localhost 8082 which is the port i've chosen flash chat dash socket and let's actually click collect and hope this works connection failed okay let's actually take a look at our server why that failed okay it was not found i know why it failed because i forgot to set up our routes when we define the routes in k2r the way we did it you can see these are still unused functions we need to call these in our routing feature so here in routing.kt we simply need to say um chat actually not chat socket um or it's actually install routing is it like that i think so if we say oops install routing and here we can then say chat socket passing the room controller which we can get from cohen and get our messages how do we get that room controller from coin that's super simple we just say val room controller by inject and then coin will inject that passing room controller here we don't need any parameters it's that simple let's relaunch that and hope that actually now works and no it doesn't application feature websocket is not installed okay that i think i know why that happens in our application class so you see we do have web sockets configured and installed however we try to configure a route that uses web sockets before we configure the feature so all we want to do is we want to swap out these two lines that we configure web sockets before the routing let's try again and it still doesn't work conflicting application feature is already installed with the same key as routing so it seems like somewhere we already have that routing feature let's take a look and configure sockets and there we go we have two of these routing blocks that's a disadvantage if you actually have this option of adding sample code enabled then cato will add all the sample code we can just remove this routing block in the web socket feature let's take a look at the others no we don't have any other routing blocks so let's now relaunch this now it doesn't crash going back to our website here clicking connect again this time it says connection established so that's pretty cool if we take a look at our server actually this one then can we see this here no not really um usually it should actually say that there was a connection if we have call logging but let's actually just send a message into that and see so we can now say message and we say um by the way we need to reconnect because i didn't attach a username we can do that as a query parameter here username is philip and now connect sorry now we establish a connection if we now send a message like i was up clicking send and yeah that looks pretty good so we sent i was up and this is what we received from the server which is basically the json string of the message which is now sent to all members in that room and yeah you can see it automatically generated an id we can also find that in our manga compass program actually if we do open that up then this is probably not how it will look like for you there will be a field with a connection string you can just leave that empty and click connect and then this will appear for you and we we can then simply click refresh here and you can see here's our new message db youtube if we click on that there is a message collection and there's our message that we actually send into yeah into our chat and here you can then edit that as you like you can delete it this is how you manage your local database now but yeah it seems that everything actually works well i know this is a long video and i can also tell you the next videos will also be quite long but this is actually a pretty cool product to get started with ktour to get started with websockets and just that server stuff combined with android it's it will be pretty cool to learn something new for you so in the next few days you will find the next part where we make the android app somewhere here not sure yet but i will include a card here and then you can simply click on that and go on with this playlist and then a few days after that i will show you how you can actually deploy that to a hostinger vps server you
Info
Channel: Philipp Lackner
Views: 7,239
Rating: undefined out of 5
Keywords: android, tutorial, philip, philipp, filipp, filip, fillip, fillipp, phillipp, phillip, lackener, leckener, leckner, lackner, kotlin, mobile
Id: Wa-e3IsNEeo
Channel Id: undefined
Length: 52min 5sec (3125 seconds)
Published: Fri Nov 19 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.