Isar Database - coding with the author Simon Leier

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
[Music] welcome to another episode of flutter original show this time I have one of the authors that I used to have it in my channel and for this show talking about another package hi Simon is with me today again well he promised and he made it to talk about isar I am very excited because he recently made good update to isar and you know it's so tempting to to to see what he has done because the Benchmark he showed you don't know you will see it very shortly it's so promising and well why also iser is important isar is a database no SQL style it's if you're building any type of application you probably need some kind of database to store your or local data or like query and do stuff especially if you are making offline first application you know and and I think one of the best solution right now there is iser and let's welcome Simon um and Simon how's it going hey Maud it's so cool to meet here again I'm good nice thank you thank you for joining again and let's quickly introduce yourself because um so many new visitor or like viewers of the channel may not have seen you before who who you are what are you doing I'm a flut engineer from Munich Germany and in my free time I try to build open source packages stuff I'm excited about mainly databases because they fascinate me for some reason seems like you are very expert on that cuz you have a bunch of packages that it's all around databases um so Hive was one of them in this channel another one is yeah of course do you want to you want to quickly yeah do you want to explain quickly what is isar and uh what is Hive I think these are the two main packages that we're going to talk about it today and and what makes is are like exceptional like you know compared to Hive and then you know things that are related good had the stage is yours tell us about these two packages okay so I'll start with Hive it's a very simple key value database it's entirely written in DT and it uses its own storage format to store your contents on the disk but it has some problems mainly it does not support multiple isolates it can be slow at times especially at app startup it doesn't support queries well so you have to implement them in dark which can also be slow and it doesn't have indexes basically it's a simple database but if you have advanced needs I don't know build a chat chat application build an email client uh task management system anything like that you you likely are looking for something more advanced like CQ light but the problem with CQ light again is I find personally I find it a little difficult to use and I just wanted to build something that is really fun to use really fast you cannot make a lot of mistakes and I hope to try provide good documentation and just make a system that works perfectly well for flutter that was my initial goal with Isa and I think you made it so I I'm in love with isar and that's one of the and also you are a fantastic Community contributor and also author so that's why you're here and we're talking right now about isar to make um others you you're making others de other developers life easier by building these packages so um isar let's talk about that you said it's a similar to SQL light and you know no SQL probably approach database right do you want to explain about that like for those who actually have not used any nosql or even SQL do you want to say something about that sure so Isa is made of three components basically There Is Isa core which is the back end that is written in Rust it takes care of all the heavy lifting it executes your queries stores your data manages different threats and all this fun stuff then there is the ISA generator it basically looks at your code at build time it analyzes your Isa classes and it builds code that allows you to interact with Isa core in a really easy way and it makes sure that at compile time that that you're not making any bad mistakes and then there is the ISA package itself which at run time is a lightweight layer around ISAC that helps you talk to it and the difference to CQ light for example is you you don't need to care about your schemas for your tables you don't will not have to write queries using strings and instead it's more like object Builder Style with static extension me methods that prevents you from making any mistakes as good as it's possible that sounds good um one of the interest interesting thing about Isa are that I fascinated me as well is that you used rust to get the best performance uh for your database you want to quickly open up the repo of Isa and just walk us through what you've done there very quickly yeah so let's first look at ISA qu the r back end oh first we have have to mention one new feature of Isa for so in the past Isa had a native custom storage solution but with version four there's a new CQ light storage solution so all the features you know from Isa before still work but now you you get to choose whether you want to use the ISA native engine or you just want a wrapper around CQ light for example if you want to use the same database from native context from Android widgets for example and from that then you just choose the CQ light engine and you get a regular CQ light file that you can just use from Android so what you're saying is like ISO is kind of like Backward Compatible with SQL light SCH like databases as well like if you I have already SQL light I can actually hook it into isar and also use maybe native engine of it right yeah yeah you can do that it always but when you say that also one more question sorry go ahead no sorry uh another question is then in this case that means do I am I going to have SQL queries and stuff like that or still I'm going to stick to the nosql approach so how does that work when I have yeah so you only change a single line of code in your code base to switch between the new two engines all the other stuff is works exactly the same and wow the ISA cite engine just converts your nosql queries and and tables to CQ light form it that sounds well that sounds cool let's let's go through this native part the the Ross part so let's let's look at the interfaces basically everything in Isa resolves around the instance an instance is basically a database F and it has a name directory and so on and it supports transactions so you can start read and write transactions to have an atomic view on your database um you can start cursors to quickly iterate through your database you can insert and update stuff and the cool thing my favorite part is queries of course and if if we take a look at how queries work in the native instance for example here we go this is all the boring stuff here the ISA cor supports these different filters basically mhm for each type there's a between query then strings have some extra ones lists have can compare any uh item this between a specific condition and then there is stuff like nested quaries if you have embedded objects ISO supports uh basically to store a tree of objects and you can querry nested fields and then of course regular and or and not conditions and all of these are implemented this file so if anyone is interested it's actually much easier than you would imagine just go to the N fil this file that's interesting so that uh rust uh package that you've created how do you communicated with the the dart layer or flutter layer yeah so in the beginning do ffi that's what the the interface you use to communicate with native code is called it was a big pain but the team has done a great job Def and out team and now it's actually really easy to use there is great documentation and a really cool thing there is ffi Gen I think it's called and it generates the bindings to call native code so if we look at this these are the bindings generated by that ffi gen and they call the the rust functions if we search for query function for example yeah yeah MH so this one iterates the query and and gets the next object and this code is horrible to write and it's so easy to make a mistake but ffi CH just generates it for you and then you just have to call this function and it calls your native rust or C or whatever function MH but that yeah of course this is one of the interesting interesting features that was added to I think dart tree at least for stable yeah stable dart tree and so that means your package is also compatible with dartree right so yeah um okay that that's very cool I think one of the one of the interesting thing about this um architecture that you showed us right now the the I think one of the benefit that I've seen is the huge performance gain you want to show The Benchmark that you've made so people get some feeling of like how fast these eyes are yeah but first I have to put like a disclaimer Benchmark benchmarking databases is super difficult and most of the time just unfair because different databases have different capabilities and you never can 100% compare them so I recommend to choose a database and see how it works for your workload and only if it's too slow choose another one that being said Isa does does pretty well in most use cases as you can see for inserting and querying it's especially fast compared to like the most common competitors like I said it's not only always a fair comparison but I I try to be as objective as possible and as you can see even the ISA CQ light engine is quite fast which I found pretty shocking but yeah yeah and I also tried to make the ISA storage format really compact so ideally it should always used like 30% Less storage than other databases apparently CQ light is even better though mhm how did you make this Benchmark anyway do you use any framework or anything or it's like a manual test by yourself uh no it's just it's just different uh queries that are executed 100 times or so and then I measure the time and yeah I think it's here yeah yeah sounds good sounds good um one last question about ISR and then let's get uh to the implementation part of it the fun part per programming so um this well the iser right now is a database that you target uh your targeting flutter applications um does it mean it doesn't work on Dart because you know dart back end is growing and I guess having actually a database in backend well some kind of maybe caching mechanism or stuff like that it always makes sense and it's always needed and does it work on backend to like a pure Dart application that's a great question so yes and no um if if we look at the repository Again by default you have to add the ISA package which is just a that package and works in any context and that then you have to add the ISA flut libraries and this one as it says in this comment contains the binaries for each architecture the ISA cor binaries and unfortunately D does not yet support B BL binaries so if you want to use Isa in a d only context you go to the GI page to releases choose your version and download the binary the binary for your CPU architect ction that's really cumbersome and and annoying but the good news is the do team is currently working on a feature called native assets and it will allow Isa to download the binaries for you and it should be super easy to use you don't need the ISA flut lips Library anymore and Isa will just work everywhere but I think it still takes a few more months to hit the stable branch mhm yeah it sounds good awesome and maybe we should also talk about web support yeah actually that was I said last question but I actually wanted to ask this does it also work on web yes because you You' written that on Rost and then you know well that that's also good question and the ISA native Library the the native backend Library does not work on on web because unfortunately web does not called memory mapping and virtual memory and that's what what it relies on but fortunately we have the second engine the CQ light engine and if you compile to web Isa will just compile CQ light to web assembly and use cqq light if you are in a browser so it will work without you noticing any difference mhm oh that's that's amazing have you considered to adding Maybe particular web engine for uh for for Isa like for example using index DB like because it's pretty popular on on browsers right it's also pretty performant I considered it and fun fact Isa version two contains an index DB engine the problem with index DB is it is has very different performance on different browsers in Safari it's it's pretty good in Chrome though not so much if you store a lot of data in index DB it gets super slow and I struggled a lot with optimizing it and having the same features that until I eventually decided CQ light and web assembly that's the way to go okay so the Benchmark that you showed like it's probably running on mobile right yeah that's that's on an iPhone yeah yeah okay so you it's probably nice to see The Benchmark on web too see that's a good point yes I didn't even try it myself so it will be a surprise yeah so we'll see but okay fun part so we now know about isar the architecture of it and it's pretty fast and we also know how flexible it is let's get into the fun part do some programming let's do um I think the best one is well let's get it started see how we can Implement that um the best is you show us the way that you are going to use isar in your project let's go cool so what do we want Sorry by the way that your let's do it one more time uh your your uh simulator is not here oh yeah good point sorry there you go let's go again okay let's do it um should we maybe store a user object like the users of our app yeah cool so we create a user class and we give it a name now we give it a username and name and maybe yes the email and password maybe not uh I see I see co-pilot is helping you a lot for that we need a Constructor okay so this is a very basic out class and you only need two things now to make it work in Isa so first of all you annotate it with collection uh tables in Isa are just called collection and then you have I think generally in no SQL databases in many of the nosql databases in we we call a collection is a uh like containing a lot of documents Which documents have these you know properties so if people don't have these mindset of nosql it's like collection a bunch of document and each document a bunch of property so collection SQL EX in SQL um word that is translated to tables rows and columns exactly so the collection would be the table and the individual user objects would be the rows in escolite exactly but one more thing we need to tell Isa is a unique ID so it can identify individual objects and in this case we will just use the username and we anate it with at ID and that's it so un unfortunately until we have static meta programming Isa relies on uh code generation code generation yeah okay so there it is and dop build I always forget this one I think it's bu underl yeah it's not bu it's the not build Runner I think up run yes this looks good oh did we not add the build dependency you did no you have to add it actually I was oh it's there it's there yeah build Runner it's there have youall um flut I get okay there we go that's how I cheat usually that's why I never remember the name so Isa will now an analyze our class and if it thinks it's valid it generates all this stuff in here so first of all it's the schema so it knows what your CL is then it builds a way to serialize and deserialize your object and then down here all kinds of static extension methods that make it easier to build queries and one thing I I'm often ask is doesn't all this stuff make my app bigger and slower and the cool part about flutter is it has something called tree shaking so only those methods that you actually use in your code will be included in your binary and the rest will just be thrown away so most of this will never make it to your production good um so I I expect that you also consider nullability and like everything that darts gives us yeah okay for sure um actually you can freely change a feel to be nullable or non-nullable even if you already already have your uh collection in production so Isa will automatically handle previous Fields with different nullability and give you a reasonable default value that's cool very cool um now the the next step is to open an instance and like I said either instances are basically I think it's like a CQ light instance and it's just a single database file containing multiple collections so let's do that it's isa. openen and then it expects you to give it the schemas you want to use and they're always called the class name and then schema so in our case user schema and a directory in a fluted app you would get the directory using something like path provider you will see it later but we here we'll just use the the current directory and then in addition you have some some options you can provide let's take a look you can give it a name if you want multiple instances you can here choose the engine either Isa or cite this one is how big your database can be it helps Isa optimize how much memory it allocates then new in Isa version 4 is you can encrypt your database this one reduces the storage size when the app starts and then the inspector which we haven't talked about at all one of my favorite Parts yeah this I have two question I have two question if you go to open actually it came to my head which is interesting so if you open uh open open again to see the properties um you'll see you said name for having different instances why would you want to have so I have a scenario in my head I just want to also say say that why would you want to have different name for the exact same schema or exact same like database let's say like different instance of the same thing you can for example use it to model your your data I've seen it before for example if you have a chat app you can use a new instance for every chat room chat but that's not what I recommend actually I would only use instances if you have different configuration for example if you want to store sensitive user data in an encrypted database but most of your app doesn't need encryption then I would create two instances one encrypted one unencrypted one of the scenario that I came to my my head quickly for that is when an app um uh like adds a feature that you can have multiple user to log in and potentially like user want to have different storage but you can't really get rid of the storage you need to keep them there but if you log in with another you are the same user probably but using different username or you know so you so it may happen right so you may log in with two different email into one app to get to different you know things for example I think this is also can be for for that use case right that's your user yeah yeah that's perfect when you say name actually this can I I can send the pr and I I can be a contributor to okay sounds good um another one is about encryption key this is super interesting I think in the part that we mentioned about these users potentially I think we want to encrypt this database right I see this is optional do you do any kind of encryption like behind the sense or we explicitly need to pass an encryption key and what is this encryption key by the way like it's you said a string can I pass a just as a letter all of these are good questions so by default Isa does not encrypt a database that being said most os's especially Android and iOS strongly protect app private files and I think even encrypt them by now but on Windows or Mac for example your database file will be somewhere where the user can access and read it that encryption is quite slow I mean it's not about Isa encryption general costs a lot of performance yeah so the default is not to encrypt the database now what does it do if you provide a a string first of all the native ISO engine doesn't support encryption so you would need the secq light engine this one mhm and it just uses cql Cipher and the reason why I why I chose this path is because c2l Cipher is industry standard basically and implementing encryption yourself can be dangerous if you over Overlook some some small detail it can actually be uh security vulnerable vulnerability and to your other question if you just provide a CQ cyer will use your a string and make it into a secure encryption key it will notice that it's too too short and it will generate a real strong key for you behind the scenes okay so then in this case if I encrypt the database on every read and write the data will be encrypted and decrypted or how does that work like you in you encrypt the entire database when you open and when you close or or every read and write will be encrypted like it's it's something in between it has some in memory caches so if you do uh if you read the same object repeatedly it will notice and keep it unencrypted but decrypting the entire database would would use a lot of memory so of course it decrypts whatever it needs and then it it throws it away afterwards mhm and another interesting one you showed me is compact on launch what does that mean like you shrink my data yes basically so imagine if you write 1,000 objects in a row and then they are also written to the database file pretty much in that order now if you delete the first 900 objects the database has a whole there's a lot lot of empty space and Isa will try to reuse the space later but if you have for example on a really lowend Android device if you have really little storage you might want to reuse the space immediately and then you can provide some conditions that Isa will check on at start and if all of these conditions are true for example if it this one if it can free 50% of your storage or if the file is bigger than 20 megabytes then it will try to remove all of this empty space and basically rewrite the entire database file it's quite an expensive operation but if if you really need the storage it might save you I see interesting and well regarding inspector then let's uh continue implementation and then you show me what does it mean inspector okay like I said we have an instance here so was pretty easy and by the way would you would you like clearly like create this instance in main before even you run an flutter application or would you do it differently for if you have like a console then this is probably fine but as you can see this doesn't have async or a weight so it's a blocking operation it will basically block your entire threat until it's done and while it's quite fast like 200 milliseconds or so it will still cause chank because in those 200 milliseconds there will be no frames so it's a good idea to use the open a sync yeah open a as method oh and not use it here but in something like a future Builder where you can avade the future that is returned by this it actually Returns the future and in the meantime still display like a progress indicator or something so I wouldn't do it like this i' create a a future Builder a stateful widget and then create an new instance while showing a loading screen or sum makes sense makes sense oh sounds good let's uh then this is now going to open an isar you do have a counter app so we talked about this basic maybe we should uh follow back to the counter app and see the other implementation um so so I think we can yeah we can let's do it one um one thing I'd like to show here is because we have so many properties in the counter so basic I'd like to quickly show of how how trares work so you is do that dot and then users because your class is called user and then you just say where to start a query and if we disable cor pilot for a second yeah okay you see all these generated query methods em between contains ends with all of these things Ison knows it can do with the the specific t p property it generates methods and the cool thing about this is you can not spell the property wrong you cannot use uh tring method that is unsupported for integers for example this one only this allowed for Strings and the second cool part is it statically checks your query for example if we say and and and again it doesn't work because this is an invalid query it only allows those properties that are allowed in this specific position for example we can we can do another comparison and then we could can use something like or or and again but it will statically check that you did make a mistake awesome then you we can use find all to find all the uh all the matching objects but let's head over to the counter which it's much simp simpler that was that was very nice yeah so here again we have a a collection and it's just called count it has an integer ID and a step number basically how often you press the count button yep and here we're in a flutter app so we actually have to give Isa a real directory because most operating systems have specific requirements where you allow to store data and since Isa stores a file on your disk you have to tell it where it's allowed to start so the first part which I always forget and screw up is this one to initialize flutter and to be allowed to use this method which is provided by a package called path provider and what it does is for every uh operating system or platform you run on it gives you a valid directory where you can store the data so we get a this is not needed actually we get a directory and we provide it to ISA and then we we start the in this example again we we did the hacky approach of implement of initializing Isa in the main method if you have a production app don't do it create yeah go the exm mile and create a future build mhm um would you so that the directory is required um if you need to always get application document directory for mobile app especially why would you do that as a default value there because it's to to me it looks like it's it's because of another dependency for your package right probably yeah and because this package is a flutter only package and I want to work in the D context so yeah okay okay makes sense I I wish there was a better way I tried all kinds of things I even tried getting the path uh directly in Rust but it also caused issues on Old Android phones so unfortunately you really have to provide the directory and it it's not automatic yeah yeah okay good then we we head into the main app and it's just a material app with an appar some text and a button that calls increment counter this is probably familiar because I stole it from the flut example project yeah and increment counter gets interesting again because one thing we didn't talked about is transactions and transactions are a way to look at the database state in an atomic way it means Isa supports uh multithreaded access and parallel read and write but often you want a consistent view of the database so it doesn't change un basically while your method is running and have inconsistent state so if you run something inside a transaction either read or write transaction the database will not be influenced from somewhere else in your code and for right access to the database specifically it is required to have a transaction and the reason is writing is rather heavy and and costs a lot of performance compared to reading reading is super cheap but writing can be heavy and what I've seen people do is have a for Loop and writing in that follow Loop without transaction and if Isa would create an implicit transaction it would create hundreds of transactions so I made right transactions required so you're more likely to put the for Loop inside the transaction right here so Isa only has to create one transaction yeah okay that's nice that you thought about that too it's it's very nice so does it mean also like when you say Atomic way so if this transaction fails so you kind of roll back to the previous state of you know the database and what it what it was right that's a very good you kind of Follow That MH whatever goes wrong inside this closure inside this function all the things you've done to the database inside it will be rolled back to the state before the trans transaction fine this allows you to to if you want to delete a user and also delete all his comments and you something goes wrong while deleting the user then you would have often comments slotting around for example and this makes sure that your database is after the transaction still in a valid State and you don't have any weird things floating around yeah mhm nice so here we use the put method and what it does it just inserts whatever object you give it here into the database and but potentially also updated right usually put is like insert update so the first parameter here is an ID if we go back here it's the ID and the number of steps or clicks we have done so um as an ID you could either uh use whatever ID you want here or you use the isa. auto inrent and it will look at your collection find out the largest ID you've ever inserted and increment it by one so you will get an ID that has never been used before because in this case we don't want to overrun the existing counts we just want to add one more if we used an ID that was used before then the existing object would be overwritten with our new one here MH yeah okay so this operation right now let's let's if you want to translate it to simple English means now you open a transaction then you start adding a row here which is a count and you add one more to that uh collection which is the counts collection in this case so in this case if I have like a for Loop let's say if I click 10 times I'm going to have 10 records in my collection am I right yeah let's let's simulate what happens here so yes if we just start the app our database looks something like this just an empty yep empty list and then as soon as we H the button for the first time it will start a transaction look at the existing IDs there are none so it will set auto increment to zero and it will add a count object with ID zero count one then if you hit the button another time it will find the existing object increment the ID by one so the new ID is one and the count is still one yeah and so on exactly this happens a bunch of times the ID gets updated every time and now that we hit the button five times the database looks something like this five count objects from 0 to four each with a step value of one we could have a super count super increment button that has a step value of 10 and we could increment the counter by 10 every time for example yeah interesting um so I guess now this is where I wanted to ask my question about Inspector Inspector means that I can explore my databases schema as well or it's just for debugging it is mainly for debugging unfortunately it isn't implemented for Isa version 4 yet so I can't show you a live example but we can we can head to the GU page again because I think there's a screenshot it looks something like this I see on the left you see the different package the different collections you have you see how many objects are St how much space it uses down here you see the different instances if you have multiple you can choose between them and then up here no let let's set here down here you see a list of all the objects you can scroll as you can see it's paginated so you can scroll through all the contents in your collection and then if you try to find a specific object you can use the query Builder up here to to build queries and in debug mode uh see what is in your database and the coolest part you can even edit your objects as you can see right here here I Chang the content to test and if I hit save it updates the app immediately and this allows you to to change the database content and see how it affects your app in real time in nice I think can I then in this inspector at the end when I made my query groups whatever then copy the code as well no you can't but that's an amazing idea I was actually thinking because it it happens right so you want to make a query it just happens that you play around with inspector even for SQL I remember I always had my like something open like when I was working in myql I had my PHP my admin somewhere always playing around get the query working and then copy and paste it somewhere that's an amazing idea I don't definitely it I I give you so many tips today so I think I'm just joking uh yeah but um this is a nice feature I I think it's going to be very useful for for many devs um playing around especially for like let's say complex databases where you have so many objects so so many collections it's hard to get your head round of queries what I want to do and also it's very hard to get it right yeah to see to see what is going on for example if you have a background isolate that cleans up your database occasionally how do you check whether it it works you can do so right here and it will update in real time if your cleanup works correctly yeah fantastic but this is cool so thank you very much let's let me just ask you my final uh question and then uh ask you to give me some advice afterwards and then finish this show um so I see you have you have a way to add different adapters somehow like native SQL or you call it engine yeah uh to to I R2 is there any way that you think at some point later you may even add an adapter to work with um poster SQL MySQL and you know I other ones like creating a om kind of package I think so yes I think it would be really cool um adding a new engine or adapter is a lot of work and a lot of testing is required because databases are really critical to be tested with so the only thing that stopped me is the time but I it's definitely on my road map and I think about it a lot because I think it would be super cool to just flip one one property and have your your code work on the back end with the real backend database so this is an awesome idea and I I actually got it as a fe feature request multiple times but I just haven't found the time to to really do it I guess one thing I can encourage whoever is watching right now this video I think one thing that is encouraging or motivating authors of these packages right now like Simon please go ahead to GitHub there is a sponsor probably link or somewhere else I'm sure uh Simon has a website or something that you can contribute also financially because it just helps Simon to figure out oh you know maybe thousands people are interested and they contributing every month's I don't know $5 and then I get 5,000 maybe I can just free up my time and work on this you know I don't know if this is something that you are passionate about or you want to do it Simon but at least this gives some motivation you know that that people are also interested to support you please go ahead and do that because thank you but just as much as financial support I really love to see people using Isa people showcasing their apps opening issues opening poll requests and so many so many people do it and it makes me really happy and is the one thing that motivates me every day to uh push another unit test that's fantastic well then uh thank you very much if you want to give us uh as always the last words like tips tricks anything about iser that you may want to share right now I know that we just touched the surface are probably a lot more to isar hopefully you will come back one more time and we go through the details of isar um but right now if you want to tell tell us any tips tricks or anything about iser just go ahead EST stage is yours your last words oh yeah I'd really love to overhaul the documentation and use for example ZB to to have real time examples and one thing I noticed is especially for beginners the documentation is too complex and I sometimes fail to see those issues because for me it looks obvious but then I I see many people struggle with a specific piece of code and it would be really helpful if you could just open issues if something is too complex if you have questions open poll requests to make it easier so that that helps a lot and I really appreciate it I I will uh write down all the gethub repos Simons Twitter and everything in the comment below so reach out to him please open isue and as as he said it's motivating to see that you are actually using and struggling and he sees that you know he can fix it for you but he needs to see it as well you know uh very nice uh Simon I never get tired of talking to you and per program with you h i you need to promise One More Time come back and go into the details of implementing maybe maybe more complex yes I always enjoy talking to you on this show and I I I think everyone else will do and thank you very much and with that said then have a good day see you next time thank you so much for having me have a great day a
Info
Channel: Majid Hajian
Views: 1,592
Rating: undefined out of 5
Keywords: Flutter, Dart, Tutorial, Learning, Isar, Isar DB, Flutter Isar, Flutter Local Database, local, database, introduction, learning, new, skill, flutter, dart, programming, code, software engineering, isar, isar database, local database, local storage, sqlite, cross platform, flutter tutorial, flutter course, flutter explained, flutter widgets, app development, flutter android, flutter ui design, android development, flutter ui, flutter developers, mobile development, Database, Db, Programming
Id: InTuwdeTNJ0
Channel Id: undefined
Length: 52min 33sec (3153 seconds)
Published: Fri Feb 16 2024
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.