Software Development Engineer
Android ShareChatSoftware Engineer
Android Nspira Management Services Pvt. Ltd. 08/2018Android Developer
Shibi Infolab Pvt. Ltd. 08/2017I'm currently working at ShareChat. So, I joined ShareChat 2 years back in 2021. When I joined, I joined the consumer experience board. And, like, the task was to take care of all the screens related to consumers and enhance the UI and UX of consumer screens. So, initially, I started working on the profile. The code base of the profile was written in 2017, August 28. It was in MVP architecture using Java. Anything that we wanted to change was very hard to change in that code base. It was very complex. So, my task was to design the profile using Jetpack Compose and MVVM architecture. I started working on it from scratch. I worked on that using Jetpack Compose and the MVVM architecture. Apart from the consumer experience board, I've also worked on the onboarding part. In onboarding, my task was to implement silent authentication, using which we reduced the OTP cost, like, which we use to send to users. So, before I was working with a utility-based startup based in Bangalore. My task was to create an application from scratch named Under Pi, a video downloader, which is available on the Play Store. I was also maintaining their application called Pie Music Player, which had around 50 million plus users. My task was to perform maintenance. I used to fix all the bugs. I improved their ANR rate from 99.2 to 99.9. Like, I started my journey in 2017, after college, I worked in a startup in Hyderabad, which was a very early-stage startup. So, like, it was an early-stage startup, and I was a standalone developer there. I created this application from scratch.
Just try to chase and deploy it to manage. So when we talk about state, we talk about UI state. We can use MVI architecture. Using MVI architecture, what happens is intent goes upward from view to view model and, view model sends this intent to the model repository to take care of it. Once the data is back from the repository to the view model, the model creates the state and gives it back to the UI. So this using MVI or MVVM architecture, we can do it pretty easily. And, of course, if we are using Jetpack Compose, in that case, we have to use stateflow. Even with the XML, we can use stateflow and we can observe this stateflow in our composables or in our overridden method. And so whenever there is a state change in the view model, it will be observed in the view, be it composable or view. And then we can change the UI. And also, we can use something called sealed classes to store these states. So sealed classes will have three states, like, for example. One is loading state, one is the data state, and one is an error state. Suppose you tap on something to give us some intent to remodel. And so initially, the loading state UI will be in the loading state. And once the data is back from the repository, the view model will update the state from loading to data state, and the UI will be changed. And if there is any issue or there is an error from the repository, the model can update the UI from data to error state, and the UI will be changed according to that.
Code testing is going well. So, in Android development, what happens is we can write unit test cases. In all the projects that I have written, the test coverage was around 85 to 90%. And so what we can do is we can mock some of the classes. We can use Mockito and JUnit, and using these, we can mark our classes or repository or anything. And then on top of it, we can use doReturn or doThrow. These APIs are available. So, suppose we are mocking some class and there is some function in the view model which we want to test. In that case, we'll be writing test cases, and like, whenever the method succeeds, in that case, we will be doing something with the doReturn method or doNext. And also, to do this, there are multiple types of tests. Unit tests using Mockito, and you can fake classes. You can create fake classes or you can mock classes and do unit testing. Apart from this, we also have something called UI testing using Espresso. We used to use Espresso to do UI testing. And this, we used to provide API mocks for different APIs and different server APIs. And like, we used to provide automated YAML classes where, as soon as there is some screen that loads, it gets the mock data about your API data, and then it does integration tests or your UI tests.
So, and there are different considerations you can take into account when implementing dependency injection. So we in Android, we have both Dagger. We have Hilt. We have Coin. So, I've been using Dagger and Hilt in my project to do dependency injection. So we have components. So in Dagger, suppose we have a component to which we define in our application class. This creates our dependency graph. So and in this component, we provide the different modules that we create, and this creates the dependency graph first. Now, suppose one of the considerations you can take is, suppose you have a login component, which should be available only until the login is not done for a user. In that case, you can keep that instance of login component alive only until that point. Once the login or sign up is done, that login component should not be there. So in that case, what we can do is you can create a subcomponent. And using a subcomponent, you can create a subgraph inside the parent graph. You can create a subcomponent. A subcomponent is just like a component. And you can instantiate a subcomponent inside your application class. That's how the instance was available throughout the app life cycle. You would create this subcomponent in your login activity, so that the instance will be alive only until the login activity is alive. When the login activity is destroyed, this instance of subcomponent will also be destroyed. Apart from this, when you are creating a multi-modular project, one more consideration that you can make is when you're creating a multi-modular project. In that case, also, you need to provide dependencies for different components to these different modules. One thing that you can consider is, different components should have different scopes. So you need to see which is which repository or class is a singleton, and should be available throughout the life cycle of your app, and which should be available for the life cycle of your activity. So according to that, you can provide different scopes, like singleton scope or activity scope for your classes or components.
Yeah. So, to manage your application history installation after the process in Android, we have so although we have a view model with us, but if the process happens in Android, even the model is destroyed. So in that case, we rely on saved instance state and on restored method of Android. So what happens is that whatever bundle our data that you provide to that bundle in on saved instance state method, that data is stored outside of your process application process. And when your application process is recreated again, in that case, that data will be using some inter-process communication. That data is given back to your process. And using the restore instance state method, you can get back to that data. Even inside the view model, we have something called saved state handle, and whatever data that you get inside on create or on restore instance state method in those bundle, that same data will be available to the view model also using a saved instance state handle bundle. So using this, you can manage the application and store data.
How do you approach writing unit tests for a complex Android feature? So to write the unit test, it's the same thing. If your feature is complex, in those cases, there will be different repositories, different classes, helper classes, and all that you would have created. You have to provide mocks or fake implementations for those. So, using the Mockito library, you can provide mocks for those classes. Or, if there is any abstraction, you can create a fake implementation of that abstraction, and you can provide that using fakes. And you can also use Stubs and Spies to provide your dependencies. Once those are available, you can use them to run different methods to do unit testing. Now, one more thing you have to consider is, suppose you have created a database. In that case, you have to write unit tests on the Memory of so you should be considering that your database will be available until the memory is alive. So, you'll use the API that provides different APIs. One of the APIs just keeps the Room data alive until the application is alive. So, you can create those. Now, if the feature implementation is complex, in that case, you can also have a different module itself for your feature unit test purpose. So, different things can be provided from that.
Here, I feel you are creating a config builder instance here. Okay? And on that instance, you are in this code base in this code snippet on the instance of config builder, we are calling set feature enabled method. Now if you see the code, set feature enabled doesn't return anything. But we are on set feature enabled method, we are again writing build, we are calling build function, which is not possible actually. Until and unless we can we can run build function only on the instance of config builder, we cannot run build on set feature enabled method. So here, to run build, we have to use something called the builder pattern here where set feature builder has to return the instance of config builder itself. Only then the build method which we are running on set feature enabled method will be possible. Otherwise, it is not even possible. It will throw a compile-time error.
We have a Kotlin singleton here. And we have a counter, intCounter, and a function. And in the function, we are incrementing the counter. So here, one thing I can see is that in a multithreaded environment, 2 threads can try to access the counter field. And another thread can try to increment the counter value. And when the 2nd thread is trying to increment the counter value, the 1st thread might get the wrong or unassigned value for the counter. This might be an issue that can happen here.
To architect an Android application that adapts to multiple screen sizes and resolutions, what you can do is whenever there is an image or any drawable that you are creating. In those cases, you could create those drawables in different resolutions using SDPI, MDPI, and HDPI. Even for resolution, you can use 360, 480, and 240 DPI. So, what happens is at runtime, whatever be the actual device on which this application will be running on, at runtime, it will select the particular drawable as per the resolution and screen size. And if you have four, three, four drawable resources for a particular device, v2, ldpi, xldpi, mdpi, and hdpi. In that case, it can directly access the hdpi and the resolution will be as per the device. Same for the screen size. We can have 360, 480, and 240 dp images where, as per the device screen size, it will adapt to the particular image which you adapt for phone sizes.
When I create a new feature branch, or an Android app in a collaborative environment, I follow these steps. One thing is, whenever you are creating this feature branch in Android, your feature branch should include all the classes or modules that you will be creating. First, you have to push the code for that. And once the code is pushed, then only you should start accessing that particular module from different modules. So, when you do this, what happens is, suppose you are accessing that module, everyone will have the code.
Have you interacted with any real time database? I did have, I have interacted with the real-time databases using database inspector. So, you connect your device and the debug APK that you have in your device. If the database for that is not encrypted, you can directly access those databases. If you are using Zoom database, you can directly access it. If you are using SQL and C4, you might not be able to access it directly. You can still do it using some hacks.