Android Build an MVVM Clean Architecture CSV File Creating and Sharing App - Android Studio Tutorial

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
what's up guys and welcome to this new video about building a clean architecture mbvm file export app in this application we will collect or Generate random data which could be in a real project data from a sensor data from a Bluetooth device or from the network from the database and once we have retrieved enough data we can generate a CSV file and once it is generated we can share this file to any destination we want let me quickly show you this when I click on start application then the data collecting or generating is already started and let's say we want to collect 400 000 data then I can click on General file now the user gets informed how far the generating progress actually is and once it is finished we can then click on this share icon and share this file to any destination we want I think this is a really cool example for clean architecture and mbvm because we will see how powerful this is and where to put the CSV logic where to put the file saving logic because we need to save a temporary file which we can then share and that together with updating the UI accordingly can be sometimes a little bit difficult but we will go through this step by step and also this is a cool feature because in a lot of applications you want to provide the user feature where you can actually share some kind of data and in our case this is CSV but this is not limited to CSV you can also apply the slot sheet for sharing pictures or other media all right okay enough talking I'm here in an empty chat pick compose project in case you use XML for your layouts you should be also able to follow along because we will have a really simple UI and most of the logic will be in the domain and in the data layer and in our view model let's check what we have already inserted I have created this three colors which you can find in my github's repository which is linked down below together with all the dependencies then let's go through the dependencies we need for our application in the project build.gradler we have the dependency for dagger Hertz because we will use dagger Hill for dependency injection in case you don't know what dependency injection is I will explain this when we use when we make use of dagahertz dagger Hill is just a framework which helps us with this powerful principle then we have the plugin for dagger Hills as well in our build.gradle for the module we have the CSV parser dependency the DECA health dependencies and also the compose navigation dependency and then up here in the plugins for our module we have kotlin caps and the dagger Hills plugin as well the first thing I would like to do is to set up our two modules in a package structure we won't have a real multi-module application here but we will kind of simulate it we will have on the one hand a core module which will only contain one file later but this file could potentially be used in multiple modules and then we have our export feature which is the main module here where we put in all the logic the core module as I already said will only contain one file we can straight through this we will create a new kotlin class or file and then we will call this resource this will be a sealed class for this resource class I will just paste the code it's not important to understand this in detail it's just kind of a wrapper class which we can wrap around return types and then we can differentiate between success error and loading and we can provide additional information like an error message or like some loading informations for example this class can be used in any application you can make use of this in any feature any return types when it comes to data processing for example it's not it important to understand this in detail you will also see how this works in practice later and in case you don't want to type this off you can of course also find this in my github's repository then we can close the core module we won't need it anymore and we can go to the export feature and here we will create a new package called Data another package called domain and the third package called presentation these are all three packages or three layers within the export module in clean architecture you normally have this three layers data domain and presentation and we will first start with our data layer in here we will have another package called file and this package will contain the logic for the file creation for saving the file and here we can create a new kotlin interface and this will be our file writer interface this interface will Define what we actually need for writing such a file and the concrete implementation will be saved up for later and also our repository class will later make use of this file writer interface and it does not need to know what's behind this interface and we can then use any kind of implementation behind this interface for our file writing the only thing that needs to be known from the repository later which will make use of this file writer is what we need to insert or needs to pass in such a file writer functioner and what we will get returns we can say suspend function write file and this will need a byte array and will return a resource of type string and this string will be then the path of our file which we will get when the file writing was successful and we will also have a companion objects Constable file name and this is equal to file export app and then we can realize this interface with a concrete implementation and in our case we will use the internal file system of Android but we could also use the externals file system of Android or even shared preferences the only thing that needs to be done is to realize this function in our file package we can then create a new class called Android internal storage by writer like this and this file writer will realize the file writer interface and then we can click on Implement manbus and provide a concrete implementation of this write file function for getting access of device directory of our application we need the context and we can get this from add in checks Constructor and this is an annotation for that so degahertz now knows that in here something needs to be injected and in this case we will have the context and for most of the cases we need to say degahertz how to inject this we will see this later we need to Define an extra file for that and there we can Define how some classes some objects needs to be injected but for the context this is a default one which dagger Hills actually knows how to provide it so we can let it stay like this and in here in our over right right file function we can say well date time is equal to calendar dot get instance dot time because we will append the current dates and the time to our file so that we can see when the file was actually created then we have a valid formatter is equal to a simple date formats and we will provide this specific formats and we will also have an underline here and then we can say return save file we will pass the byte array the firewriter dots a fire name which we defined in our interface plus underscore plus formatter dot formats date time like this and now we need to Define this function which would be a private function say file and this takes a byte array and a file name like this and it will return a resource or type string as well we first need the files directory we can get this from our context dot files directory this is the file directory of our internal storage for the application let's go to the device file explorer and for example here in data and data we have all the different applications and also this application I created and each of this application has some files here a cache and in this application there's nothing into but this is exactly this directory our internal application files directory because we will use this for saving the temporary file and this is a lot less pain than using the external one especially after Android 11 I think then we will create a subfolder where folder is equal to file and we will pass our file directory and we will call this folder export app and this will create the folder if it's not there yet and if it's already there then this functioner will just do nothing and now we want to delete everything that's inside this folder in case it actually exists because each time we export a file it gets saved into this folder and we don't want to save multiple files we only want to save the current file and when a new export a new shell request is there then all the old files need to be deleted otherwise we would blow up the storage we can invoke this the leads directory and pass the folder and now we need to Define this delete directory functioner we can make this down here private functioner delete directory and this will take a directory of type optional file and will return a Boolean in case you want to check on this function up here we don't check if this was actually successful or not but yeah in a real project maybe you should check this we will say return if directory is not equal to null and directory is actually a directory because such a file can also be for example a CSV file then we will have a well children is equal to directory dot lists this will lists all the judgments of this directory so everything that's into this directory if children is not equal then there is something into this directory or I in children dot indices so we will hover over all the different files and directories which are in the current directory where success is equal to delete directory this will now be a recursive call directory and we will pass a children at the index of I so well this is more of a general formula to delete really all files it will go inside the directory and if there's another directory and in this directory another directory then this recursive call will go down to the deepest file and delete everything and if this is not successful then we will return false and below this four no below this if call we will say directory dot delete and after that we also need to define the else if case so if this is not true row then we will say else if directory is not equal to null and directory is file because this can also happen or this is in our case always true because we only save files in this directory then we will say directory dot delete and if none of this is the case then we will say false and then with this functioner we really destroy or delete everything which is in the folder we pass here all right then we can check if the folder does not exist then we need to create it and I need to correct me I set that the folder will get created up here when we invoke this but yeah this is not true this just tries to get this folder and if this is not existing then we need to create it manually we will say if folder and cardio this will try to create a folder if this is not successful then we will try it with folder and cardias in here we could do some kind of error logging but I will leave this up to you then we can say well file name is equal to file and then we pass our folder and we will say file name which we will get from our function called parameter and then we will also append CSV like this and then we need an output stream well OS is equal to file output stream and we will pass our file name and this file output stream allows us then to write to this specific file then we can say return try valid path is equal to UI Dot from file and we will pass our file name dot path and if this is now we will return resource dot error and we will say for the message could not get a file path like this and now you can see how we use this resource class we can return an error and provide an additional error message like this and in the repository or in a view model or somewhere else where we collect this return types we can then check on the different resource types and we would see that this is an error and then we could read the error message all right below this the well path we will say OS dot right and we will pass the byte array which we will get also in this functional here and then we will say resource dot success and we will pass the path because we need this path later to make the sharing possible because when we share a file we need the path where the file is actually located and then a so-called provider can provide us this file to share it and if something happens inside this drive log we will catch the arrow and this Arrow can normally just be of type IO exceptional when dealing with files here we could again do some error logging but I will just return resource dot error and for the error message we will pass e dot localized message and this can be null and for that case if it's null we say unknown error because then we actually don't have a clue what what's going on in the finally block of the strike hatch we say OS Dot close because in each case either this is successful or this is an error we need to close the output stream otherwise we could have some kind of memory leak or undefined Behavior so we should always close it and we can do this in such a finally block this was it for dealing with the file logic now we can go to our data package and create a new package called converter and in here we will Define their behavior the logic for converting data and especially converting it into a CSV byte array in the converter package we can create a new interface called Data converter and the same principle here we will just Define a function which needs to be realized to convert data and later it does not matter which is the underlying logic so it doesn't matter if it's a CSV converter if it's some kind of other data format converter it just needs to realize this function and this function will be convert sensor data and we will pass an export data list of type list export model we need to Define this data class we will do this after we Define this interface and this will return a flow of type resource of type generator info and we also need to Define this generator info data class and let's do this right now so that we get rid of this errors in our converter package we can create a data class called generate info and this will have a byte array of type byte array which is optional and null by default and the progress percentage which is of type integer and 0 by default this will indicate the equivalent progress percentage later when we create the CSV file because when we have 1 million or 2 million entries then this could take a while and we want to inform the user how far the progress actually is then we can close this and here oh this is called generate info now we also need to define the export model but since this is a domain object this is our business object which the application is about we can't Define this in the data layer in our converter package for example we do this in the domain layer in the domain layer we have a new package called model and in this model package we create the data class called export model and this will have two properties on the one hand property sensor data of type load which is not further specified and a well time of time long so the current timestamp when the stencil data was created then we can go back to our data converter and import this in export model class and then you can see we access the domain layer from our data layer because the data layer is the layer we are currently into where the data converter is defined and we access the domain layer and in terms of clean architecture this is find the data layer is able to access the domain layer otherwise if we would Define this export model inside our data layer where we defined this generate info for example then we would later need to access it from the domain layer and the domain layer is the core of our architecture and it shouldn't be dependent on the data layer for example and this is why we Define this export model in the domain layer and this generate info is only accessed inside the data layer so it's fine to Define it in here you will see this later when we access the different objects and I will also try to leave a comment when we access different things in terms of clean architecture all right okay then we can start defining our Z3 data converter in our converter package we will create a new package called CSV and there we can put into everything that has something to do with CSV in our case this will be just our data converter CSV and this will be a class and we'll realize our data converter interface then we can again click on Implement members and we need to overwrite this convert sensor data function and we can also put this in a separate line so it's a little bit more readable remove this to do and first of all we want to define a private function which helps us later to get the CSV writer we will say private functioner get CSV writer and we will pass the writer in here of typewriter and this will return an ICS V writer in here we have a nice buildup functioner we will say return CSV writer Builder and pass the writer and then we will say with separator and for that we go below our overwrite functioner and Define a companion object and here we will have a const well separator which is a semicolon you could also use the comma for example and we can also Define the header data for our CC file later which will be an array of for the First Column sensor data for the second column the time and up here we can pass our separator we will say with quote character CSV writer Dodge no quote character and then with Escape character CSV writer.default Escape character with line ends CSV writer dot default line ends and well this are just example values I have also from the documentation or from stack Overflow or something like this and well this works fine with this case but maybe you have some special cases when it comes to a CSV converting then you can of course adjust this then we say dot build and we still have a compiler Arrow because it needs to be a character let's use single quotes here then the error should be gone and now we can go to our overwrite function for our override functional we will apply a so-called flow Builder because we now build a flow which can be then collected later from our repository or View modeler and we can always inform the user how far the CSV converting actually is first of all we want to emit resource dot loading and we will pass the generator info and we Define some default values the progress is zero and the byte array is null so this is fine we don't need to pass Arguments for that then we say well writer is equal to stringwriter well CS3 writer is equal to get CSV writer and we will pass this string writer then we can say well values for one percent is equal to export data list dot size divided by 100 and we will put this in parentheses and add plus one because there's a single case when this export data list is below 100 then this will become zero and later we need to divide some value by this and we would divide that by zero so we will have plus one and to explain what this value is about we have for example the export data list of 100 000 values then we will divide this by 100 and the result of this would be 1000 then we need would need to process 1000 values to actually have one percent finished and this value is for one percent indicates that then we say VAR already converted values is equal to zero because at the beginning we haven't converted any values yet then we say CSV writer dot right next and then we will pass our header data so the first row will contain our header data sensor data and time for the description then we have exports data list dot for each and here we get access to the export models now we will write the corresponding information for each model CSV writer dot right next array of and we will first say export model dot sensor data and for the second value we will say export model dot time and after that we can increase the already converted values by one and we could check if already converted values motor law values for one percent is equal to zero this is always true if we can divide this already converted values by this values for one percent without some rest this is what this modulo operator does we have for example already converted values of 50 000 and the values for one percent would be one thousand and then this could be divided and we would result in fifty percent the reason for that is that we don't always emit new values after each iteration here we will only emit new information if the current percentage actually changed we can then say resource.loading and for the generate info we will pass a progress percentage is equal to already converted values divided by values for one percent and now you can also see why we are at this plus one up here because in some cases when this results in 0 when we have very little data then this would be divided by 0 and which results and in an exception below this for each block when we are finished we can say emit resource dot success and we will pass the generate info and for the byte array we pass this time the string writer dot buffer and we say two byte array like this and for the progress percentage we can pass 100 just to be sure after that the last step is to close our CSV writer and also the screenwriter and we get a little warning here because we have a possibly blocking call a non-blocking context and we should use the dispatchers.io but later in our view model we will collect this flow in the dispatchers.io context so this should be fine and we can ignore this warning for now all right okay now it's time to put everything together we can go to our domain layer and create a new package called repository because the interface of the repository will be defined in domain layer because it will be a accessed from our viewmodel and the viewmodel then accesses the domain layer you will see this when we put everything together and defined everything that all the different accesses from the different layers are in the right order we will say export repository and this will be an interface the function of this repository will be start exports data and we will pass an export list of type list of type export model and the return type will be of type flow of type resource of type path info this path info is a data class we need to decline now in our modeler in the domain layer we will say data class path info the first property will be well path of type string which is optional and set to null by default because the path is only set when we have currently reached 100 percent then we have the progress percentage of type integer which is 0 by default then we can close the static class import it here and then we defined the repository interface in the domain layer and the realization will be now in the data layer for that we will create a new package called repository inside the repository package we will have a class called export repository implement this will realize the export repository interface we can click on Implement members and inside the export repository implement we also need access to two different interfaces we will say add and check Constructor privateware file writer of type file writer and privateware data converter of type data converter and now you can see we get the interfaces we defined before provided here and all we need to know is that for example this file writer has a functioner called file rights or right file and needs a byte array and returns a resource of type string we don't need to know about the underlying implementation and this is why this interfaces are so powerful in the test cases for example if you want to test this export repository then you could provide some fake implementation for a file writer because for this case you don't want to always write to a file system and in case you wanna switch the generating the strategy for the generating you don't want to use the CSV a dependency here you want to create the file in another way then you could just um change the underlying implementation and for this repository this file writer.write file functioner would remain the same because it just depends on the interface and not on the concrete implementation and we can also see that the export repository Implement needs to be in the data layer because it needs access to the file writer and to the data converter which are both in the data layer if we would Define this repository Implement in the domain layer then we would access the data layer from the domain layer which would be wrong in terms of clean architecture and since the export repository interface this here is defined in the domain layer everything is fine because the export repository interface does not needs to know about the file writer and the data converter interface because this is more of a concrete logic one thing that's left to explain is how dagger hitch currently knows how to provide this to interfaces it does not yet because we didn't Finance we will do this after we implemented this export repository Implement after that we will set up diagram and also Define how to provide such a file writer and such a data converter but let's take care of this start export data function first in here we have our dataconverter DOT convert sensor data and for that we will pass the export list and map the results because this convert sensor data Returns the flow of type resource of type generate info here we get access to the general rates info like this and we can say when generate info and now we can check on the type of this resource class this is really cool because we can say if we saw when generate info is resource.success is resource dot error or is resource dot loading and in each case we can apply different logic and we can also get access to the information of this resource because we will get when we have a look at this law we will have this resource and it's wrapped around the generate info so we will also get access to this generate info if we want for example in this success block let's start with the easy case in the arrow block we will say return at map and we will return resource dot arrow and for the error message we will pass our generator info dot error message in case we are currently loading then we will say return map resource dot loading and since we are currently in a mapping function we need to map this result this generate info to the type path info so we will say path info and for the progress and percentage we simply say generate info dot data uh question mark dot progress in percentage and if this is another then we will simply pass zero okay so if we are currently loading then we will just map our generate info to our path info and we will pass the progress percentage up to the view model because there we can then show the current percentage and because we can then also check if this is accurately loading answer and we can show the dialog where the progress indicator is and lists all the or show the different percentages depending on how far we convert it okay in the resource.success block we need to apply a little bit more logic because when we are into this block we know that the channel rates does the data data converter successfully generated the CSV data then we need to store this data in the file system and now you can also see that this repository class is really cool because it puts together different data responsible Logics so on the one hand we have the file writing Logic on the other hand the data converter logic and we don't want to make them communicate with each other therefore we have this export repository which puts everything together in a central place in here we will say generate info dot data dot byte array and since this is optional we will apply dot let and then we can say when well result is equal to filewriter dot write file and we will pass it this will trigger the file writing we can then open a block and do such a resource.success loading and error check again we can say is resource.success is resource dot loading is resource dot arrow for the error case we will say return add map again resource dot error error message will be a result dot error message the loading case should not happen but since we should make this when expression exhaustive and also map this to a path info we just say return atmap resource dot error and for the error message we will simply say unknown error with the loading case should not happen I mean that the file writer does not emit loading values so this can't happen in the resource.successblock we can say return atmap resource dot success we will pass a pub info for that the path will be our results dot data and the progress percentage will be 100. this was it for the start export data functioner to put everything together by writing and generating CSV data in which the current percentage later for the view model the only thing that's left is to switch the context in here when our low block ends we can say flow on dispatches dot IO because before we have seen in the data converter CSV that we need to do this in the dispatchers.io because this is uh possibly blocking call their warning won't go away but since we use this flow on operator the whole flowchain down here is executed in the dispatchers.io curating context and this the compiler just does not recognize that this is also executed in this context and this is why the warning won't go away we still have a little error here a return expression required in a function with a block body okay we don't return anything but we can change this with an equal sign here oh no we can't actually we need to take this whole mapping function and put it behind this equal sign let's go down and copy all of this and cut it out then remove these two parentheses and for better readability we should also put this in separate lines so this mapping function needs to be put behind this equal sign like this and then we still have an error because we have a question mark here something can still return an optional type I think it's here in this mapping function let's try this out return at map resource dot error because well this shouldn't also be HEPA unknown error occurred okay all right then the arrow is gone and we are good to go to create our view model and collect this data for the UI but before we actually do this we will Define the DECA Health module to tell dagger Hilt how to provide this file write of for example how to provide this data converter for that we can collapse the data package and create a new package called di in this EI package we will have an object called exports module and for dagger Hilt to know that this is the module where we defined different classes and how they should be provided we need to annotate this with ADD module and add install in and for the ad installed in we say Singleton component double double colon class and now we can start with defining the different classes and how they should be provide we need to annotate this with ad provides and add Singleton we will say functioner provides by writer and this file writer as I mentioned earlier needs their context and here we can say application context context of type context and this will return of type by writer and we will return the concrete implementation of bits the Androids internal storage file writer and we will pass the context the next thing we want to provide is our CSV writer we will say add provides and add signal again functional provides data converter of type data converter for the return we will say data converter CSV and this does not take any arguments and the last thing that needs to be provided is the repository it provides that single functioner provides exports repository this will take the file writer of type file writer and the data converter of type data converter and this will return export repository and since Decker hill now knows how to provide a file writer and also how to provide a data converter because we defined it up here we can just pass this as arguments and for the return we say export repository Implement and we will pass the bywriter and the data converter for the Decker Hill setup we also need to go to our main activity because this is our entry point here we can say Android entry point and the last thing we need to do is to go to our root package and create a new class called export application this needs to inherit from application and we need to annotate this with build Android app and in our manifests we need to specify the app name the export application and then dagahertz is set up and should make the dependency injection for us now we finally can go to our presentation layer and take care of our view model and the corresponding screen and in here we will have a file export States data class which is used by our viewmodeler and we can also put this in a separate package let's create a package called State and put this file export State into there this file export States will have all the state variables which can then be observed by the UI like loading information progress information if the shell data is actually clicked if the shared data is ready and so on we will have a well is General rating loading of type pool which is false by default well is share data connects of type Boolean and faults as well by default well is shared data ready of type Boolean and false well share data URI of type string which is optional and now by default then failed generating of type Boolean and ports as well well generating progress of type integer and this is zero by default let's also correct this little typo generating progress and then we are good to go to use this file export States data class in our viewmodel in the presentation package we will create a new class called export viewmodel and we need to annotate this with Edge build view model because Decker herd needs to read a viewmodeler a little bit more specific so we need to annotate this it will also have an inject Constructor annotation and in here we will provide well exports repository of type export repository and now you can see that it accesses the domain layer because the export repository interface which we provide here is defined in the domain layer if we would also Define the interface in the data layer then the new model would directly access the data layer and this would in terms of clean architecture be wrong and this exports view modeler needs to inherit from new model first we have a private bar export list is equal to mutable list of export model and this will be just the list we fill manually for the UI normally you would retrieve this export model data class objects from a Bluetooth device or from sensor data from the network or somewhere else then we have for more connected data amount by mutable State off and this is zero by default and private sets we need to import the get and set value for the mutable state of delegates this mutable State Bar is just to show the user how much data are actually collected we could also somehow use the export list size but I would like to make this a state and increase it each time new data arrived then we have a bar file export state by mutable State auth and this will take the defaults states of our file export States objects we already defined this in the data class with all the faults by default values we make this a private set as well so that we can only set this file export state within our new model and then we can create a functional generate export file the first thing we want to set is the file export state to find export state DOT copy is generating loading to true so that we can then start to show the dialog where the progress indicator is and the current percentage value after that export repository dot start export data and we pass our export list to list we don't want to pass a mutable list we want to pass an immutable one this is why we do this mapping after that we can say on each and gets access to each path info object which gets emitted in this flow chain so from our CSV converter and then over the repository which Maps it from the generating info to the path info and then it arrives in our viewmodeler and we get access to each of these values inside this on each block then we can check on the path input if it's resource dot success is resource dot loading or if it's resource dot error in case there's an error we can say file export state is equal to file export state DOT copy failed generating to True when we are currently loading we will say Pub info dot data dot let because this data is of type optional file export state is equal to file export state DOT copy generating progress is equal to pathinfo.data dot progress percentage so we get always the current percentage of the data converting and can show this to the user when we are in the success block we can say file export state is equal to filexport State DOT copy is shared data ready is equal to True is generating loading is equal to false share data URI is equal to Pub info dot data dot path and the generating progress is equal to 100 and let me think about it in the error case we can also or we need also sets there is generating loading to faults because otherwise if we would receive an error then the loading bar would still indicate that we are currently loading because there is generating info is set to True up here and if we don't have this it wouldn't be set to false again the last thing we need to do for this flow is to say launch in because otherwise the flow would remain cold we need to make it hot that it really emits values so we will say launch in viewmodel scope before we manually insert got all the exports lists data we need to have two additional functions on the one hand on share data click so when the user clicks on the share data icon we say file export state is equal to filexport State DOT copy is share data clicked and we will set this to true then we can copy and paste this and here we have on share data opener and here we set this to false so this will make sure that the user can click on the share icon multiple times and the share dialog opens multiple times otherwise it would only open the first time when this is set to true so we will set this to false after the dialog shows where the user can select the different apps you want to share the file with and then he can click on this multiple times alright okay let's go up to this positioner and Define the init block for this view model here we first need to define a chart up private bar collecting job of type job which is optional and now by default let's import this job and then we say collecting job is equal to viewmodel scope.launch here we Define an infinitely while loop so a while true we will delay each iteration by two milliseconds and then we set collected data amount to plus equal 160 then we will insert some values at all list off and I will just copy and paste them you can find them in my github's repository but you can also try this out with just one value order sharing should work and also Define creation I would like to just try this out with one or two million values so each iteration have 160 of this random generated values get inserted so we will have tons of values which can be created for the CSV file and then the loading dialog shows up for a few seconds otherwise it would disappear immediately and one thing that needs to be done is in our generate export by it we need to stop this job we say connecting job dot canceler because we don't want to collect any more values if the user actually clicked on the generate exports or generate a file button okay this was it for our view model now we can create our export screen for that we will go to our presentation package and create a new kotlin file called export screen this will be a composable export screen and it will take our export view model of type export view model and we can generate this with this Hilt view model function first of all we need to access the file export date is equal to export viewmodel dot file export State and we also need the context which is equal to local context dot current and now we need to start a launched effect because showing the chair dialog is a side effects and we need to do this in terms of compose in a launched effect or in some other effect Handler we will say launched effect for the key we will pass file export State and that means each time this file export State changes this launched effect gets triggered in here we check if file export state DOT is share data ready and if this is ready then we can show the share dialog we will say well URI is equal to file provider dot get uie for file we will pass the context the context dot applicationcontacts dot package name plus provider and now we need to Define find this provider we will come to this after this URI value is finished we'll say file dot filexport state DOT share data UI and we can assert that this is not optional because otherwise the shared data wouldn't be ready let's import file and now let's take care of this file provider which makes sure that's the right file is provided from our internal file system for that we can go to Resource and in the XML section we will create a new resource file called provider paths and then click ok in here we can go to this code sectional and just paste this into this will provide the internal files and the file paths and looks in the current directory you can find this from my github's repository but I think this is also not that much to type off in our manifest file in the application section below this activity we can then paste this kind of code which will be our file provider well I won't type this off here I will just copy and paste this because I'm not that good with XML syntax and this would take a while then but just to explain this and well we have a file provider here from Android X core content and this will look now in the resource where our provider path is and we create UI permissions and yeah the authority is our application ID and the provider of our application so well in short this just makes sure that a file can be provided with from this file provider we can also Define the query later for the action sends so when we actually want to share the file then we need to start an intent with the action send type and for that we also need to Define queries and yeah the mime type is every type here we could also limit this to text or some other kind of media type but yeah also just copy and paste check on this in my GitHub repository or also this can be typed off it's not that much okay all right let's close the Manifest and go back to our export screen where is it here after we have our UI we can define an intent is equal to intent and for the actioner we say action send the intense DOT type will be Text slash CSV intense dot put extra here we pass a subject for example if you want to share this to an email or via an email then you could also Define a subject and then the email subject is automatically filled in we say extra subjects and for that we say my exports data we also need to define the stream intent dot put extra intense dot extra stream and there we pass our URI after that we also need a Chooser where Chooser is equal to intent dot create Chooser and we will pass our intent and for the header we pass share with now we can say contextcompletes dot startactivity pass the context our Chooser and for the options we will pass null and the last thing we need to do is export viewmodel. on share data open because if you remember we will set this is share data clicked to false so that this launched effect gets triggered again and again if the user may want to share this data to multiple applications one thing that's wrong here is this if condition checked and this should not trigger if its shared State already because we only want to open the share dialog if is share data clicked not if it's ready if it's ready we'll then result in showing the exports or the share icon below this launched effect we can then start defining our UI we will start with the box which takes a modifier this will fill the max size the background will be set to Gray and the content alignment will be alignments.center in the Box Score we will first have a column the vertical arrangement of this color will be Arrangement dot space around horizontal alignment will be set to Center horizontally and display will also take a modifier which fills the max size then we can open the column scope and the first thing we want to show is the text connected data amount which we will get from our export viewmodeler Dodge collected data amount the style will be set to material theme dot typography.h4 the font values will be font weights dot bold the color will be our white color and the text align will be set to text align Dot Center below this text composable we will have a buttoner with an on click method and in this on click method we will invoke the exports view models generate export file functioner then we will also set button colors we can say colors is equal to button defaults dot button colors the background color will be set to Orange the content color will be set to White and the disabled background color will be set to Orange as well Spartan takes also a modifier this modifier will fill the max with by 90 the padding will be 10 DP and we will also apply a shape which will be for this button as circle shape we can also enable or disable this partner we only enable it if the file was not created yet so we can say enable is equal to export viewmodel dot filexport state DOT is shared data ready and we negate this so if the shared data is not ready yet then we can invoke it or we can click on this button and I think we have the file exports State already defined above so we don't need to get access to it via this view modeler then we can open the button scope for the visualization of the text we will say text is equal to generate a file the style will be material theme dot typography.h6 the point where it will be font weights dot Bold and the text the line will be set to text align Dot Center now we can all also Define when to show the icon for the sharing we will use an animated visibility for that to make a little bit of an animation for that when it should be actually shown we can say file export state DOT is shared data ready then we can define an icon partner with an on click method and when we click on this share icon button then we invoke export viewmodeler dot on shell data click and this will set this is share data clicked to true and back in our export screen we can go above to this launched effects then this will trigger or first the launch defect will trigger because the file export state did change and then this if condition is true and the shell sheets or the shell intent starts back on our icon partner we can then invoke the scope and Define an icon this will take a painter resource which comes from r.travel.export and first we need to import R and I think I already defined it now we don't have this export in the drawable I thought I already insert oh I inserted it in there in the XML resource file this is wrong we can take this and paste it inside Stitch rubber then we can close this and remove it from the XML resource file and then the arrow scan then we also need to pass the content description I will call this simply exports The Tint will be set to Orange and this icon button also takes a little modifier and this modifiers size will be set to 120 DP the last thing that's left is our dialog which we should show to the user when we are currently generating the file and indicates that we are currently loading and show the corresponding percentage we can do this down here and we will first start with an if conditioner if file export States dot is generating loading then we can invoke this dialog and we will pass an undismissed request or we just leave this empty but it's required to Define one then we have a column which takes a modded buyer and this modifier will fill the max size the vertical Arrangement will be set to Arrangement dot Space by 15dp the horizontal alignment will be set to alignments.center horizontally inside this column we will first have a circular progress indicator and we will set its color to whites below that we also have a text which shows the user the current percentage generating a file and then we open the parentheses and for the text we pass export viewmodel or now we can again use the file export state DOT generating progress then we append a percentage close the parentheses and apply three additional dots for the color we say color.white or colors equal to White style will be material theme dot typography dot H6 and the font weights will be set to fontbase dot medium okay all right I think we are good to go to try this out after we go to our main activity remove this default stuff and invoke our export screen we can also remove this and then we can launch the application and check if everything works okay you can see we add a lot of data and this happens really quickly because each two milliseconds and 160 data okay um we can't click on this generate file partner this is an error which should not occur well okay I found the mistake it's here we of course should enable the partner if there is shared data already it's not true because then we should generate the file I think this happened before when I set export viewmodel.file export state is share data ready and I had it like this and I think when I removed this export view model then I didn't that's the exclamation mark again okay all right we can try this again start the application all right okay we can see again that we add a lot of data and we see also that the button here is clickable indicated by the white color of the text of this partner now let's generate the file by clicking on this button and you can see the file is generated and we get updated with the current percentage the general file partner is now disabled because of course the file is already generated now we can click on the share icon and we can share the file which is named here file export app and the date in the time.csv we can share it now to Gmail or swipe and of course in your real world application to a lot of more apps we can also close this again with the back partner and click on it again and then we could potentially share this file to multiple applications let me quickly also show you the device file explorer here we can go to our application this is the file export YouTube and here the internal files directory is now empty oh no okay it updated accordingly we have this 10 MB I think it's still readable yes it is and when we click on this we can also see that this is our CSV file with two different columns and here are the random generated objects let's launch the application again and have a look at the file system if the last file is actually deleted and it only contains the new file let's click on generate file again and yeah of course we should Center this percentage information to the middle we will take care of this after we had a look on the file system still works everything is fine and then let's go to the file system we can also expand this a little bit you can see this file now and when we click on synchronize then we have an updated one and the old file is deleted okay let's close this device file explorer again also close the CSV file and go to our export screen to the dialog because we want to Center the current percentage information we can do this with this modifier I think we should only switch this to a filmex width and that it does not expand the whole size because such a dialog is centered in the middle of the screen by default let's launch the application again and have a look on that let's collect some data before we actually generate the buyer because we want to see this dialog a few seconds well last time it's collected the data faster I think well okay I think 150 000 are enough yeah you can see this was in the middle now it was centered and looked better all right okay this was it for this video I hope you enjoyed it and you could learn something and maybe you can also apply this logic in your own projects it's also cool to share different kinds of data and also to create CSV files in some cases and share them afterwards or make use of the internal storage system of Android in case you want to learn more about building clean architecture mvvm apps in Android then make sure you check out my other mbvm clean architecture application and in the future I will also do more videos about building such applications
Info
Channel: K Apps
Views: 1,434
Rating: undefined out of 5
Keywords: Android, Android State Management, Android Jetpack Compose, Android Compose, Android Multi Module Architecture, Android Multi Module, Jetpack Compose StateManagement, Android Dagger Hilt, Android MVVM, Clean Architecture, Android Best Practices, Android Clean Architecture App, Jetpack Compose Clean Architecture, Android JetpackCompose, Jetpack Compose State Management, Android Clean Architecture, Android Filesystem, Android Share data, Android Storage, Android Studio Save File
Id: OlPRY-MFqzg
Channel Id: undefined
Length: 63min 41sec (3821 seconds)
Published: Sun Dec 11 2022
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.