[{"content":"Hello! Some time ago I stumbled upon a library inside Square\u0026rsquo;s brilliant Retrofit repository called retrofit-mock that allows you to build dummy Retrofit clients for usage in tests without OkHttp\u0026rsquo;s MockWebServer or adding mocking library like Mockito or Mockk. Thing is, it\u0026rsquo;s not documented well, and it was a bit of a challenge to set it up and make it work, but the end result works quite well. So, I decided to write this article with some introduction for the retrofit-mock usage. I will shortly explain what it is, how to use it, and compare with the other solutions.\nWhat is retrofit-mock retrofit-mock is a library for Retrofit that allows you to create Retrofit API interface instances that won\u0026rsquo;t communicate with the actual API, instead returning the value you specify. It\u0026rsquo;s similar to OkHttp\u0026rsquo;s MockWebServer, but it allows you to simulate varying network conditions and doesn\u0026rsquo;t require setting up local server and storing JSON responses in your repository and it\u0026rsquo;s generally on a higher level of abstraction. It requires some setup, and in case of simple API interfaces, you\u0026rsquo;ll need to make this setup once. The retrofit-mock doesn\u0026rsquo;t do any response conversion, so you need to set an entity object directly to the mock API instance, called Behavior. After that, the object you specified in this Behavior will be returned in the every method call of the API interface.\nretrofit-mock can be used to stub actual API during development in the main app, but I used it only in tests, so I will talk about it here only in context of testing.\nSet up dependencies To get started with the retrofit-mock, you\u0026rsquo;ll need to add dependencies for Retrofit. I recommend using BOM and version catalog for that. Here\u0026rsquo;s what you need to add to your libs.versions.toml file:\n[versions] # ... retrofit = \u0026#34;2.10.0\u0026#34; [libraries] # ... retrofit = { group = \u0026#34;com.squareup.retrofit2\u0026#34;, name = \u0026#34;retrofit\u0026#34; } retrofit-bom = { group = \u0026#34;com.squareup.retrofit2\u0026#34;, name = \u0026#34;retrofit-bom\u0026#34;, version.ref = \u0026#34;retrofit\u0026#34; } retrofit-converter = { group = \u0026#34;com.squareup.retrofit2\u0026#34;, name = \u0026#34;converter-kotlinx-serialization\u0026#34; } # or any other converter of choice retrofit-mock = { group = \u0026#34;com.squareup.retrofit2\u0026#34;, name = \u0026#34;retrofit-mock\u0026#34; } And in your module\u0026rsquo;s build.gradle.kts:\ndependencies { implementation(platform(libs.retrofit.bom)) implementation(libs.retrofit.converter) implementation(libs.retrofit) testImplementation(libs.retrofit.mock) } Writing test with retrofit-mock To set up retrofit-mock, you\u0026rsquo;ll need to have your Retrofit API interface ready. For example, here\u0026rsquo;s the API interface for GitLab:\ninterface GitLabApi { @GET(\u0026#34;/api/v4/projects/{id}/issues\u0026#34;) suspend fun loadIssues(@Path(\u0026#34;id\u0026#34;) projectId: String): List\u0026lt;Issue\u0026gt; } We may have a class that uses this interface, ApiDataSource:\nclass ApiDataSource( private val api: GitLabApi, private val ioDispatcher: CoroutineDispatcher ) { fun loadIssues(projectId: String): List\u0026lt;Issue\u0026gt; = withContext(ioDispatcher) { try { api.loadIssues(projectId) } catch (e: HttpException) { emptyList() } } } Let\u0026rsquo;s test this class. We\u0026rsquo;re starting with the dummy Retrofit instance:\nprivate val retrofit = Retrofit.Builder() .baseUrl(\u0026#34;http://example.com\u0026#34;) .build() It doesn\u0026rsquo;t need to have a real base URL, but it should be present and be a valid one, otherwise it will crash.\nThen, we can setup MockRetrofit instance:\nprivate val mockApi = MockRetrofit.Builder(retrofit) .build().create\u0026lt;FactCheckApi\u0026gt;() By default, MockRetrofit instance will have random network delays, which might make your tests flaky, because sometimes delay can hit more than 10 seconds, and it will fail the request. You can set fixed delay via NetworkBehavior. You will need to create it beforehand and add it to MockRetrofit\u0026rsquo;s builder like this:\nprivate val networkBehavior = NetworkBehavior .create().apply { setDelay(100, TimeUnit.MILLISECONDS) } private val mockApi = MockRetrofit.Builder(retrofit) .networkBehavior(networkBehavior) .build().create\u0026lt;FactCheckApi\u0026gt;() Delay can be anything, and setting it to zero will disable the delay altogether.\nNow, we can proceed with writing tests:\n@OptIn(ExperimentalCoroutinesApi::class) @Test fun `empty list on error`() = runTest { val sut = ApiDataSource( mockApi.returning(Calls.failure(Exception(\u0026#34;test\u0026#34;))), Executors.newSingleThreadExecutor().asCoroutineDispatcher() ) val result = sut.load(\u0026#34;0\u0026#34;) assertThat(result, isEmpty()) } @OptIn(ExperimentalCoroutinesApi::class) @Test fun `successful case`() = runTest { val expected = listOf(Issue()) val sut = ApiDataSource( mockApi.returning(Calls.response(expected)), Executors.newSingleThreadExecutor().asCoroutineDispatcher() ) val result = sut.load(\u0026#34;0\u0026#34;) assertEquals(expected, result) } As you can see, it requires a bunch of boilerplate code to setup, but the tests end up concise and simple.\nretrofit-mock vs other tools One of the main tools for testing Retrofit clients will be the aforementioned OkHttp MockWebServer. It has similar logic behind it: you can configure it to return only one response. It\u0026rsquo;s possible to add a callback with more sophisticated logic to figure out what response to provide based on request\u0026rsquo;s path or something like that. However, it operates on the lower lever, so the response you will need to provide should be either string or binary, requiring you to store JSON files in the test resources. And that\u0026rsquo;s one more thing to maintain, because if the API changes, you will need to update these test JSONs in order to avoid running tests over older API version. However, it\u0026rsquo;s seemingly the most popular approach in the industry, so you might find more info about it online. This quide describes it well.\nYou can also mock your Retrofit client via regular mock framework, and specify returned results. But it won\u0026rsquo;t behave like a real network, because mocked methods usually return values immediately. Also, mock frameworks affect the time of test runs, because they need to set things up on first usage (at least, in case of JUnit 4). It\u0026rsquo;s not much, 1 to 2 seconds, depending on the usage, but compared to the single test runtime it can be dramatic. For example, single class test run on my machine took 1.7s with Mockk and 60ms without. This kind of difference can accumulate significantly over multiple runs of tests, and that\u0026rsquo;s one of the reasons why I choose fake objects over mocks. However, mock frameworks can provide more sophisticated tools for state verification, in case you need it.\nAnd since Retrofit uses regular interface classes, you can also create stub implementation of the API interface that will return objects as you need them. It might be simpler, but with this approach you won\u0026rsquo;t be able to verify if your API interface has all the annotations correctly placed until you will use it with the proper Retrofit setup in the runtime.\nConclusion Thanks for reading till the end! To sum up, we walked through the retrofit-mock library with the use case of testing, and considered other options we have for testing. Based on that, retrofit-mock seems like a decent option for using it in tests to stub network calls, but it\u0026rsquo;s not perfect.\nHope it was useful for you. If you have any questions, hit me up on Mastodon. Good luck and have fun!\n","permalink":"https://fobo66.dev/post/retrofit-mock/","summary":"\u003cp\u003eHello! Some time ago I stumbled upon a library inside Square\u0026rsquo;s brilliant \u003ca href=\"https://github.com/square/retrofit\"\u003eRetrofit\u003c/a\u003e repository called \u003ca href=\"https://github.com/square/retrofit/tree/master/retrofit-mock\"\u003e\u003ccode\u003eretrofit-mock\u003c/code\u003e\u003c/a\u003e that allows you to build dummy Retrofit clients for usage in tests without OkHttp\u0026rsquo;s \u003ca href=\"https://github.com/square/okhttp/tree/master/mockwebserver\"\u003e\u003ccode\u003eMockWebServer\u003c/code\u003e\u003c/a\u003e or adding mocking library like \u003ca href=\"https://site.mockito.org/\"\u003eMockito\u003c/a\u003e or \u003ca href=\"https://mockk.io/\"\u003eMockk\u003c/a\u003e. Thing is, it\u0026rsquo;s not documented well, and it was a bit of a challenge to set it up and make it work, but the end result works quite well. So, I decided to write this article with some introduction for the \u003ccode\u003eretrofit-mock\u003c/code\u003e usage. I will shortly explain what it is, how to use it, and compare with the other solutions.\u003c/p\u003e","title":"Mocking network requests with RetrofitMock"},{"content":"Hello!\nSome time ago I learned about demoscene in the BBS network in the early 90s. People have created amazing graphical images purely with Assembly code on a very limited hardware, and it was so great that even after all these years enthusiasts continue to create new demos and preserve old ones all over the world. I got inspired by the demoscene and decided to try something like this myself. I\u0026rsquo;m not familiar with Assembly (and, frankly, don\u0026rsquo;t want to touch it), so I chose to create some demos on Android using OpenGL. It might not be as impressive, but at least I can use programming language I am familiar with (Kotlin) to set up some scaffold code for the actual OpenGL graphic code. Unfortunately, the info around computer graphics and OpenGL is quite inconsistent and scattered across different places, so I decided to summarize my learning experience in one article for future reference, so other people could benefit from this too. It may seem scattered, but it\u0026rsquo;s how I understood stuff.\nIn this article, we\u0026rsquo;re going to walk through the main concepts behind computer graphics and OpenGL in context of Android and uncover the points for further reading. It\u0026rsquo;s intended for figuring out what\u0026rsquo;s it all about, but you may need some basic knowledge about computer graphics, math and Android to make the most out of it.\nBasic graphics terms and concepts This part is kinda optional if you already know how graphics works, I just wanted to summarize my knowledge and make sure I don\u0026rsquo;t miss anything. It is basic definition of the fundamental terms and concepts.\nComputers have a hard time creating visual data. They see image as a pile of binary data and don\u0026rsquo;t by default understand how to put this chunk of bytes on the screen, so it\u0026rsquo;s necessary to transform whatever you want to draw into appropriate format consumable by computer, so it can take it and draw it on the screen.\nIt was decided that the best approach to represent visual data on screen is using pixel – a single unit of data, like atom in the molecule. Each pixel has position and color. So it\u0026rsquo;s left to figure out how exactly we can draw image on the screen using pixels.\nThere are 2 ways of doing it: raster and vector. Raster graphics means that the image is presented two-dimensional array of pixels (in the simplest case), and these pixels are merely structures that hold some info about the color of the point encoded in one of the many formats. For example, we can have 3 numbers from 0 to 255 that will represent hues of red, green and blue. This way, our pixel will be RGB-encoded. There are multiple possible ways to encode a color, but the RGB is the simplest.\nIf we need to draw some complex shapes on the screen, or produce some rapidly changing animated 3d object, raster drawing would be too hard. It will involve complex math to calculate shape\u0026rsquo;s pixels and place them on the screen. And vector graphics allows us to do exactly that but without much hassle. We need to define shape with some params that we need, and the computer will do the hard work and draw the shape exactly how we define and on the fly. Vector images can be scaled with no quality loss, so if you put vector icon on the small WearOS watch or on the billboard, it will look sharp, while raster image will likely be a mess when scaled too much up or down.\nGraphics involves a lot of math, and despite computers are good at math, they struggle to draw stuff with CPU only. It was enough in the early 90s to have only CPU in the personal computer to draw images, but the CPUs were quickly pushed to the limits. And not because they were less powerful than now, but because they were single-threaded. So people created separate processing units for graphics (GPUs) that can calculate a lot of math in parallel. Modern GPUs contain thousands of cores with frequencies sometimes more than 1 GHz, so the can calculate billions of operations with ease. To utilize this power, graphics nowadays is drawn pixel by pixel in big chunks to fill up GPU resources.\nOpenGL OpenGL is a standard to describe how GPUs can draw graphics on the screen. It consists of the specification and the C-like framework aligned with this specification. It is maintained by Khronos Group and kept open source. I will shortly cover the internals of the OpenGL for the curious minds and introduce main concepts here.\nIn general, you don\u0026rsquo;t need to use any framework to draw stuff on screen. Developers can communicate with GPUs directly, and OS may have a driver for the particular GPU, so we can draw whatever we want. But you will need to write a shit ton of boilerplate code just to set things up and make the environment ready for drawing, and that will likely work only on your machine. Other GPU model may not support your code at all. It\u0026rsquo;s like manufacturing your own custom paper and pencil out of raw materials (wood, cotton, etc.) and with the provided tools, just to draw some doodles. It is suboptimal at least. But at the time of OpenGL creation, there was no other choice.\nLuckily, people realized that and started to think about generic framework for graphics that will contain the complexity of setting up stuff for each platform and offer convenient tool set for drawing basic building blocks of the image, so it can be applied for any use case and be portable enough, so you won\u0026rsquo;t need to worry too much about compatibility with other GPUs.\nOpenGL draws stuff pixel by pixel, and for each pixel we can provide a script with the logic necessary to draw this pixel. These scripts called shaders and should be written in the special language called GLSL. It is similar to C, but with some extra default stuff defined in the framework. Shaders can accept input data, which allows us to draw something that depends on a state, e.g. on the cursor movement or time.\nThere are 2 types of shaders in OpenGL: fragment shader and vertex shader. Fragment shader produces color data and similar visual stuff for the pixel, and it\u0026rsquo;s enough to use just them for 2D graphics. Vertex is a position in three-dimensional space, so vertex shaders are needed to draw 3D objects on the screen, because they can help to produce position of the vertex of the model in the 3D space and convert it to the 2D space. Since our screens are mostly flat, it\u0026rsquo;s necessary to have some way to project 3D objects on flat surface. It\u0026rsquo;s a relatively simple math that is done behind the scenes, so we don\u0026rsquo;t need to worry too much about it. You don\u0026rsquo;t need to understand it fully to be able to write shaders, but it may be helpful for the best results. Math can be intimidating for some people, but in this case you can easily visualise this math for better understanding before you actually get it. So don\u0026rsquo;t worry too much if it\u0026rsquo;s hard to grasp, take your time and practice writing shaders until it clicks.\nYou will need to write most of the code for the shader yourself, but there are a lot of helper functions in the standard library that are implicitly included in the shader for you to use. Most of them are math functions, so basic knowledge of matrices and vectors will be useful there to understand what\u0026rsquo;s it all about. But again, math is optional. Official docs are the main source of info about the standard functions, but you can refer to some guides online, such as this.\nOpenGL on Android Android was initially planned and developed as the OS for the digital cameras, so it was designed for the low spec devices with limited resources. For that reason Android uses special subset of OpenGL standard – OpenGL ES, or GLES. It has fewer features compared to the regular OpenGL, but it offers better performance and compatibility with mobile GPUs. You can use OpenGL ES APIs from Kotlin code via Android SDK bindings, or from C++ code via NDK. It\u0026rsquo;s OK to just support the latest version, since it works on most of the currently active devices. GLES is backward compatible, meaning that the hardware with newer version can run shaders written for older versions of the standard. So, if you need to maintain some older hardware or not sure if your device will be able to draw what you want, use GLES 2.0.\nOfficial guide contains a lot of info on how to draw some stuff.\nAndroid also has support for Vulkan, which is a successor of the OpenGL with better performance. But it\u0026rsquo;s more complex, and you are only able to work with it through C++ and NDK, so I would not cover it here. There is a good video with comparison of Vulkan and OpenGL.\nAGSL With the introduction of the Jetpack Compose, it became tedious to use GLES shaders on Android, because GLES requires SurfaceView to be around. You can have interop between regular XML views and Compose, but in case of the SurfaceView it can be tricky to make it work, and you might not be able to use Preview.\nWith Android 13, Google introduced their own Android Graphics Shading Language (AGSL) for real-time graphics. It is based on GLSL syntax, compiled on the spot and is well integrated with the Compose. However, there are some differences between GLSL and AGSL that are important to consider.\nNext steps To learn more about computer graphics, you can dig deeper into shaders. There are other frameworks that utilize shaders with similar syntax, such as DirectX on Windows, so if you would want to draw something on different platform or with more details, learning OpenGL shaders will still be useful.\nThere are some good tutorials for OpenGL, as well as good YouTube videos with step by step introduction, like this or this. Rebecca Franks aka @riggaroo also highlighted some learning resources in her blog post that are more specific for Android.\nIt\u0026rsquo;s notoriously hard to debug shaders, but there is shadertoy app that allows you to iterate on WebGL shaders quickly, and immediately see bugs. And when you\u0026rsquo;re done with shadertoy WebGL shaders, you can then adapt them for GLES environment.\nIf you have any questions, hit me up on Mastodon.\nGood luck and have fun!\n","permalink":"https://fobo66.dev/post/opengl-graphics-guide/","summary":"\u003cp\u003eHello!\u003c/p\u003e\n\u003cp\u003eSome time ago I learned about demoscene in the BBS network in the early 90s. People have created amazing graphical images purely with Assembly code on a very limited hardware, and it was so great that even after all these years enthusiasts continue to create new demos and preserve old ones all over the world. I got inspired by the demoscene and decided to try something like this myself. I\u0026rsquo;m not familiar with Assembly (and, frankly, don\u0026rsquo;t want to touch it), so I chose to create some demos on Android using OpenGL. It might not be as impressive, but at least I can use programming language I am familiar with (Kotlin) to set up some scaffold code for the actual OpenGL graphic code. Unfortunately, the info around computer graphics and OpenGL is quite inconsistent and scattered across different places, so I decided to summarize my learning experience in one article for future reference, so other people could benefit from this too. It may seem scattered, but it\u0026rsquo;s how I understood stuff.\u003c/p\u003e","title":"3D graphics guide for Android: OpenGL and related concepts"},{"content":"Privacy Policy Andrei Mukamolau built the WearMMR app as an Open Source app. This SERVICE is provided by Andrei Mukamolau at no cost and is intended for use as is.\nThis page is used to inform visitors regarding my policies with the collection, use, and disclosure of Personal Information if anyone decided to use my Service.\nIf you choose to use my Service, then you agree to the collection and use of information in relation to this policy. The Personal Information that I collect is used for providing and improving the Service. I will not use or share your information with anyone except as described in this Privacy Policy.\nInformation Collection and Use For a better experience, while using our Service, I may require you to provide us with certain personally identifiable information, including but not limited to usage data. The information that I request will be retained on your device and is not collected by me in any way.\nThe app does use third-party services that may collect information used to identify you.\nLink to the privacy policy of third-party service providers used by the app\nGoogle Play Services Google Analytics for Firebase Firebase Crashlytics Log Data I want to inform you that whenever you use my Service, in a case of an error in the app I collect data and information (through third-party products) on your phone called Log Data. This Log Data may include information such as your device Internet Protocol (“IP”) address, device name, operating system version, the configuration of the app when utilizing my Service, the time and date of your use of the Service, and other statistics.\nCookies Cookies are files with a small amount of data that are commonly used as anonymous unique identifiers. These are sent to your browser from the websites that you visit and are stored on your device\u0026rsquo;s internal memory.\nThis Service does not use these “cookies” explicitly. However, the app may use third-party code and libraries that use “cookies” to collect information and improve their services. You have the option to either accept or refuse these cookies and know when a cookie is being sent to your device. If you choose to refuse our cookies, you may not be able to use some portions of this Service.\nService Providers I may employ third-party companies and individuals due to the following reasons:\nTo facilitate our Service; To provide the Service on our behalf; To perform Service-related services; or To assist us in analyzing how our Service is used. I want to inform users of this Service that these third parties have access to their Personal Information. The reason is to perform the tasks assigned to them on our behalf. However, they are obligated not to disclose or use the information for any other purpose.\nSecurity I value your trust in providing us your Personal Information, thus we are striving to use commercially acceptable means of protecting it. But remember that no method of transmission over the internet, or method of electronic storage is 100% secure and reliable, and I cannot guarantee its absolute security.\nLinks to Other Sites This Service may contain links to other sites. If you click on a third-party link, you will be directed to that site. Note that these external sites are not operated by me. Therefore, I strongly advise you to review the Privacy Policy of these websites. I have no control over and assume no responsibility for the content, privacy policies, or practices of any third-party sites or services.\nChildren’s Privacy These Services do not address anyone under the age of 13. I do not knowingly collect personally identifiable information from children under 13 years of age. In the case I discover that a child under 13 has provided me with personal information, I immediately delete this from our servers. If you are a parent or guardian and you are aware that your child has provided us with personal information, please contact me so that I will be able to do the necessary actions.\nChanges to This Privacy Policy I may update our Privacy Policy from time to time. Thus, you are advised to review this page periodically for any changes. I will notify you of any changes by posting the new Privacy Policy on this page.\nThis policy is effective as of 2022-07-12\nContact Us If you have any questions or suggestions about my Privacy Policy, do not hesitate to contact me at fobo66@fastmail.com.\nThis privacy policy page was created at privacypolicytemplate.net and modified/generated by App Privacy Policy Generator\n","permalink":"https://fobo66.dev/post/wearmmr-privacy-policy/","summary":"\u003ch2 id=\"privacy-policy\"\u003ePrivacy Policy\u003c/h2\u003e\n\u003cp\u003eAndrei Mukamolau built the WearMMR app as an Open Source app. This SERVICE is provided by Andrei Mukamolau at no cost and is intended for use as is.\u003c/p\u003e\n\u003cp\u003eThis page is used to inform visitors regarding my policies with the collection, use, and disclosure of Personal Information if anyone decided to use my Service.\u003c/p\u003e\n\u003cp\u003eIf you choose to use my Service, then you agree to the collection and use of information in relation to this policy. The Personal Information that I collect is used for providing and improving the Service. I will not use or share your information with anyone except as described in this Privacy Policy.\u003c/p\u003e","title":"Privacy Policy for WearMMR app"},{"content":"General Valiutchik (\u0026ldquo;App\u0026rdquo;) is developed by one main developer.\nPrivacy Policy (\u0026ldquo;Policy\u0026rdquo;) describes how information obtained from users is collected, used and disclosed.\nBy using the App, you agree that your personal information will be handled as described in this Policy.\nThis policy in Belarusian language can be found here | Палітыка канфідэнцыяльнасці на беларускай мове знаходзіцца тут.\nInformation being collected App queries info about user\u0026rsquo;s approximate location, with your prior permission, in order to determine correct currency rates to display. Location data is not stored locally and may be sent to Mapbox Inc. third-party service for geocoding. Usage of the data by Mapbox Inc. is subject to their respective terms of service and privacy policy. You can enable or disable access to location at any time, through your device settings.\nApp uses internet connection to load info about currency rates. No personally identifiable data is collected.\nChanges to the Policy If the Policy changes, the modification date below will be updated. The Policy may change from time to time, so please be sure to review this page periodically. These changes are effective immediately after they are posted on this page.\nLast modified: 29 Jun, 2022.\nContact If you have any questions about the Policy, please contact me via fobo66@fastmail.com\n","permalink":"https://fobo66.dev/post/valiutchik-privacy-policy/","summary":"\u003ch2 id=\"general\"\u003eGeneral\u003c/h2\u003e\n\u003cp\u003eValiutchik (\u0026ldquo;App\u0026rdquo;) is developed by one main developer.\u003c/p\u003e\n\u003cp\u003ePrivacy Policy (\u0026ldquo;Policy\u0026rdquo;) describes how information obtained from users is collected, used and disclosed.\u003c/p\u003e\n\u003cp\u003eBy using the App, you agree that your personal information will be handled as described in this Policy.\u003c/p\u003e\n\u003cp\u003eThis policy in Belarusian language can be found \u003ca href=\"/be/post/valiutchik-privacy-policy/\"\u003ehere\u003c/a\u003e | Палітыка канфідэнцыяльнасці на беларускай мове знаходзіцца \u003ca href=\"/be/post/valiutchik-privacy-policy/\"\u003eтут\u003c/a\u003e.\u003c/p\u003e\n\u003ch2 id=\"information-being-collected\"\u003eInformation being collected\u003c/h2\u003e\n\u003cp\u003eApp queries info about user\u0026rsquo;s approximate location, with your prior permission, in order to determine correct currency rates to display. Location data is not stored locally and may be sent to Mapbox Inc. third-party service for geocoding. Usage of the data by Mapbox Inc. is subject to their respective \u003ca href=\"https://www.mapbox.com/legal/tos\"\u003eterms of service\u003c/a\u003e and \u003ca href=\"https://www.mapbox.com/legal/privacy\"\u003eprivacy policy\u003c/a\u003e. You can enable or disable access to location at any time, through your device settings.\u003c/p\u003e","title":"Privacy Policy for Valiutchik app"},{"content":"Hello! After the announcement from Microsoft about Android apps support in Windows 11, I was excited to try to publish my app to Amazon Appstore, so I can discover more form factors and attract more users. However, there was one caveat: Windows can only run apps without Google Play Services dependency, since it will not be present in Windows and in Amazon Appstore. So, I started looking for workarounds.\nMy app was using only FusedLocationProviderClient API from Play Services, and only to load current location of the device, so I found a way how I can achieve similar results in more Kotlin-friendly way with help of existing Android SDK Location API. It turns out that there was not so much info about Location API in Android, since even official docs are advising to use Play Services.\nIn this article, I will tell a bit about Play Services and LocationManager, warn you about some caveats of Android\u0026rsquo;s Location API and provide the code that loads current location with LocationManager.\nWhat\u0026rsquo;s wrong with Play Services? Play Services is a separate app that allows devs to use various aspects of Android OS independently of vendor modifications. It contains various useful services besides location. Quoted from its Play Store page:\nGoogle Play services is used to update Google apps and apps from Google Play. This component provides core functionality like authentication to your Google services, synchronized contacts, access to all the latest user privacy settings, and higher quality, lower-powered location based services. Google Play services also enhances your app experience. It speeds up offline searches, provides more immersive maps, and improves gaming experiences. Apps may not work if you uninstall Google Play services.\nLooks promising, but main concern about Play Services in the industry is that it is treated as a part of Android OS. It means that developers rely on them heavily, and if there\u0026rsquo;s no Play Services on some device, apps will break and won\u0026rsquo;t be able to work. And there are almost no workarounds for that. So it looks like severe vendor locking, which can be dangerous in the future. It\u0026rsquo;s not exactly a monopoly, because there are some alternatives, but still, there are cases when Play Services are not installed on the device or there is no way to install them, e.g. Amazon devices or Huawei devices.\nFrom developer\u0026rsquo;s perspective, I can provide my own concerns, since I cannot speak for everyone.\nPlay Services\u0026rsquo; Task API that is quite inconvenient to work with in today\u0026rsquo;s codebase. It doesn\u0026rsquo;t fit well with coroutines or RxJava. Plus it\u0026rsquo;s nullable API, so you can get null result from some request, and e.g. in RxJava it\u0026rsquo;s not acceptable answer, despite being valid in some cases (Location that you retrieve can be null). Because of that devs are forced to use some workarounds, sometimes dirty ones, which is not a sign of a good API in the first place, in my opinion. I understand that it was created before RxJava and coroutines took over Android development, and at that time it was decent solution, compared to AsyncTask and bare threads or Executors, but nowadays it feels quite outdated and not very suitable for modern apps\u0026rsquo; approaches to async work, although you can use some adapters for it for Rx or coroutines.\nLicense of Play Services libraries. Google seems to use some kind of open license with limitations, similar to AOSP, for its SDKs. They contain some proprietary code and it\u0026rsquo;s obfuscated on the client side. Also, the Play Services app itself if a closed-source, so we can only guess what\u0026rsquo;s going on there. That\u0026rsquo;s more of a nitpick, since you can use these libs freely in any application without limitations, but for critical part of the infrastructure of many Android apps, I feel like it can be more open, so community can at least see how it all works, or be able to send patches without relying on Google to do so.\nPlay Services APK is immense. It takes up too much space on the device and contains a lot of things that no app might use on the device. I understand that it\u0026rsquo;s done to reduce sizes of other apps by moving common code to the one app, but I would prefer to find better way here. For lower end devices, Play Services can easily take over the whole available space, so user will struggle installing anything else. Situation with this worsens every year, e.g. recently they decided to include TensorFlow Lite runtime in the Play Services to reduce the size of the apps that use it, so they will only need to store models.\nPlay Services client-side SDKs are usually working in the same way: they create some intents to Play Services app and load data from there. It is well hidden behind obfuscation and tedious interface of aforementioned Task API. They are quite thin because of that, but still.\nSo, if it all is concerning you as well, let\u0026rsquo;s check what options we have:\nHuawei Mobile Services (HMS) – great set of open source tools developed by Huawei to replace Play Services. People tend to use them both in one app, e.g. by having special flavor that uses HMS and other flavor that uses Play Services. But it is available mostly for Huawei devices, so we cannot use it everywhere. Well, we can, probably, because HMS SDK can download and install HMS APK to use it, but it can have compatibility issues with non-Huawei devices. Also it is quite similar to Play Services, so drawbacks will also be the same. MicroG – open source replacement for the Play Services. It\u0026rsquo;s a great tool that masks as Play Services and does the same job mostly, but it requires root access to be properly installed, and thus it\u0026rsquo;s mostly available in custom ROMs used by enthusiasts. Write custom solution – best option in terms of control and matching requirements, but it can take a lot of time depending of what part of Play Services you would like to replace and what resources you have at your disposal, e.g. custom backend for the payments processing or some maps provider that load highly detailed maps from your own satellite (just kidding). Turns out that we have only limited possibilities to replace Play Services, or at least some parts of it. Let\u0026rsquo;s turn back to the initial point of replacing Play Services Location API.\nWhat\u0026rsquo;s this with the location? Location handling in Android is historically not so well-designed. LocationManager is a system service on Android available since API 1, and it was used for various operations related to device location since the beginning. In the beginning of Android, people were using it for everything. However, it was requiring too much boilerplate code even for simple operations. You can still find some old Android docs for it to see how complicated was the setup of it. Boilerplate code is not fun, of course, but in this case it\u0026rsquo;s the type of code you would write once and forget about it for a long time. Google suggests not to use it directly anymore because they created a replacement. But since we decided not to use this replacement, let\u0026rsquo;s find out more about how LocationManager works.\nLocationManager operates on a very low level of location data. There are various providers of the location info, e.g GPS, cellular networks, Wi-Fi, etc. And LocationManager does not automagically gathers info from all of them, we need to explicitly ask various providers of the location data for the location, and these providers may have very old location, or no location at all. Also, LocationManager may not be available on the device (although with little probability) or location access can be disabled on device, or some provider can be disabled for some reason (and won\u0026rsquo;t give us any data because of that), so we need to check for these cases as well. In short, there are tons of nuances that we need to account for.\nHow to request current location with LocationManager Simplest way to get location would be to query each of the available providers for the last location they\u0026rsquo;ve got. It will be something of a simple traversing of the list of available locations. If we require the most recent location, we can request current location from each of the available providers, but it\u0026rsquo;s more asynchronous operation, so it can take some time. We can just try to request some particular provider that is reasonably precise for us, e.g. GPS or cellular, but in my experience they are not so reliably available on device, sometimes GPS can be disabled with no reason or cellular will take some time to determine location. My tests have shown that sometimes these physical providers were not updated recently, or were not yet requested since the start of the device and thus were empty, but some other, less obvious providers had quite recent location.\nHere is the code I ended up using for getting current location:\nprivate val noLocation by lazy { Location(0.0, 0.0) } @RequiresPermission(Manifest.permission.ACCESS_COARSE_LOCATION) suspend fun resolveLocation(): Location { val locationManager = context.getSystemService\u0026lt;LocationManager\u0026gt;() ?: return noLocation return if (LocationManagerCompat.isLocationEnabled(locationManager)) { var location: android.location.Location? = null withContext(ioDispatcher) { location = locationManager.getProviders(true) .asSequence() .map { locationManager.getLastKnownLocation(it) } .filterNotNull() .filter { SystemClock.elapsedRealtimeNanos() - it.elapsedRealtimeNanos \u0026lt;= locationFixTimeMaximum } .maxBy { it.elapsedRealtimeNanos } } location?.let { Location(it.latitude, it.longitude) } ?: noLocation } else { noLocation } } Let\u0026rsquo;s walk through the code step by step.\nFirst of all, I used some default location with zero latitude and longitude as an indicator that we don\u0026rsquo;t have a location available to us. It\u0026rsquo;s just an illustration to keep example short, I would recommend to use some sealed class hierarchy to clearly separate different cases that can happen.\nNext, we switch to IO dispatcher and start iterating over the list of the available location providers. I used sequence here just for fun, although there might be a lot of providers on the device, not only physical ones like GPS or cellular, so sequence can be a better choice. Or you might need to perform more operations on the list, which is also a good use case for sequence. But in most cases you will be just fine with the regular list operators.\nWe try to get last location from each of the providers and ignore those that don\u0026rsquo;t have any location. And among available locations, we search for the one that is most recent. You might need to set some different maximum age for the location for various reasons, or even no maximum age at all, but keep in mind that some providers can have really old location that might not be actual anymore. For example, some of the providers on my phone had 3 days old location as the latest, so this timestamp filtering seems reasonable enough.\nConclusion We reviewed the case against usage of Play Services Location API, and looked into the proposed alternative for retrieving last known location using LocationManager. It turns out to be not so simple, but allows us to get rid of the dependency on Play Services. It can be useful if you only need Play Services for getting last known location and nothing else.\n","permalink":"https://fobo66.dev/post/play-services-location-migration/","summary":"\u003cp\u003eHello! After the announcement from \u003ca href=\"https://blogs.windows.com/windows-insider/2021/10/20/introducing-android-apps-on-windows-11-to-windows-insiders/\"\u003eMicrosoft about Android apps support in Windows 11\u003c/a\u003e, I was excited to try to publish my app to Amazon Appstore, so I can discover more form factors and attract more users. However, there was one caveat: Windows can only run apps without Google Play Services dependency, since it will not be present in Windows and in Amazon Appstore. So, I started looking for workarounds.\u003c/p\u003e\n\u003cp\u003eMy app was using only \u003ccode\u003eFusedLocationProviderClient\u003c/code\u003e API from Play Services, and only to load current location of the device, so I found a way how I can achieve similar results in more Kotlin-friendly way with help of existing Android SDK Location API. It turns out that there was not so much info about Location API in Android, since even \u003ca href=\"https://developer.android.com/guide/topics/location/migration\"\u003eofficial docs\u003c/a\u003e are advising to use Play Services.\u003c/p\u003e","title":"Migrate from Play Services Location to Android's LocationManager API"},{"content":"Hello! Recently I received copyright infringement notice about my favorite pet project, Bookcrossing Mobile app, and was asked to remove the app from Play Store. On this sad note, I decided to discontinue its development completely and keep its code open and untouched. In this article, I will describe my journey with development of this project, some cool tricks I\u0026rsquo;ve learned, as well as provide some description of the project for anyone who may be interested in it. Project will rot quickly and will become mostly unusable after a short period of time (but still may serve as a reference), but my experience working on it might be useful for those who are looking to start some mobile app project alone.\nBackstory I\u0026rsquo;ve started this project to explore RxJava and Firebase, also to build up my portfolio and to learn. Idea came to my mind when I was browsing Play Store in search of the app to exchange books. I\u0026rsquo;ve been participating in bookcrossing movement since 2010, when the first shelf appeared in my hometown. It was not systemized, and a lot of books were not registered on the website, mostly because at that time our local site was quite inconvenient to use, and the global site was only available in English. So, after not finding the official Bookcrossing app on the Play Store, I decided to create my own app for bookcrossing that will make it easy to release new books and that is convenient to use.\nI\u0026rsquo;ve created a list of desired features, but there was almost immediately appeared one more problem: neither main or local bookcrossing websites didn\u0026rsquo;t have any API available. So I had two options: HTML parsing on client to extract some data or go with my own backend.\nI\u0026rsquo;ve tried to go with Google services, and started researching Google Cloud Platform stuff for backend. GCP is cool, and it offers a lot of good services, but it all required a lot of effort to invest, and that wasn\u0026rsquo;t what I wanted. Ideally, I would have preferred to spend as little time on backend development as possible, so I can focus mostly on app side.\nThus, I turned my look to Firebase. It was at that time finishing merging into Google, and there was not so much services they offered, but they promised great developer\u0026rsquo;s experience and more cool features to come in the future. But anyway it was quite enough for my needs: Realtime Database was quite enough to store basic data that I had, Cloud Storage fitted perfectly for storing cover images for, and Authentication promised to be a really straightforward tool to handle users without much headache. Plus later I\u0026rsquo;ve added Ads and Analytics as a side effect. There was even convenient FirebaseUI wrapper that helped with binding Firebase services with UI, I\u0026rsquo;ve used it to display cover images from Firebase Storage and for displaying books in RecyclerView via special Adapter class from this library.\nI had an eye on RxJava for a long time, it seemed for me the good choice for the most Android apps due to its good threading abstractions, concise API and functional programming tint. Moreover, it was just hip back in the days, and people were using it extensively in the projects, as well as asked about it on the interviews. So I decided to use this project also as a polygon for experiments with RxJava, to learn it by practice.\nThese factors shaped the initial architecture of the app and helped decide how it will look like.\nInitial development Once I figured out what to do, I chose then-popular MVP architectural pattern for my new app that was implemented with help of Moxy library. It was quite straightforward, given the amount of code on StackOverflow ready for copying and pasting right into the project. For non-trivial issues I\u0026rsquo;ve picked some libraries from GitHub with decent amount of stars. I followed examples for Firebase setup and for Moxy, without quite thinking about fitting it into the MVP pattern, so I ended up with a lot of Firebase-related code in views and a lot of business logic in presenters, all mixed up. I even tried to write some tests, but it looked for me like there was not so much to test in terms of business logic, so I haven\u0026rsquo;t added any tests. To be frank, it wasn\u0026rsquo;t quite possible to add tests in that situation, because of a coupling business logic with UI, as well as lack of support for tests on Firebase side: I discovered that it was impossible to call any of the Firebase SDKs, even those that are seemingly unbound from the Android SDK, it just immediately crashed because of lacking app ID, and it turned out to be quite hard to set dummy ID for tests. All of these factors contributed to the decision to postpone writing tests for later. This wasn\u0026rsquo;t a red flag for me back then, since I knew that not many teams are actually writing tests. But after spending enormous amount of time in writing tests for existing codebase of questionable quality, I now always ask about tests during job interviews.\nRelease After I\u0026rsquo;ve implemented most of my ideas, I decided to prepare the app for release, but perform some manual testing of the whole app beforehand. I did test each feature in isolation while developing, but now I was checking everything in combination, each user flow I can come up with. There were some annoying issues with styles for toolbar, but nothing critical. I\u0026rsquo;ve spent quite a lot of time (couple of days, actually) trying to fix toolbar appearance, but nothing worked. So, out of frustration and poor health condition I\u0026rsquo;ve postponed release for later.\nAfter few months I got back to releasing process and decided to let that pesky toolbar bug into this release. Spoiler: I\u0026rsquo;ve fixed it only three years later with help of Chris Banes\u0026rsquo; Insetter library. Also, the other bugs that were here I decided to fix after release, since I considered them minor.\nApp went live on beta track at some date, and after that I\u0026rsquo;ve noticed quite a few crash reports in Crashlytics. They were caused by the lack of data sanitization and error handling. I\u0026rsquo;ve fixed them immediately by adding these things.\nMostly app was functional, but first users have uncovered scenarios I wasn\u0026rsquo;t prepared for. For example, some unusual user navigation flows were leading to crash because of uninitialized variable. It took me the whole day to find a root cause and fix it. Variable was initialized in the wrong lifecycle callback.\nAnother interesting issue was with ads config. I\u0026rsquo;ve sourced Firebase Ads integration code from multiple places, and it was complete mess, to be honest. Even more, it didn\u0026rsquo;t work on release build because of misconfiguration. It took me 2 days to figure out what was wrong and configure it properly. Somehow it slipped from me until I noticed that there is no ads in the Play Store build after couple of months after release. If there was more users, it would cost me quite a few bucks.\nI haven\u0026rsquo;t promoted the app anywhere, and I haven\u0026rsquo;t bought installations. Not because I\u0026rsquo;m against it, just didn\u0026rsquo;t want to bother with marketing stuff and pay for advertisements, more wanted to focus on improving quality. Even without this, there were more than 1500 installs of all time, most of them unfortunately followed by uninstalls.\nBig refactoring Time passed, I was slowly improving some features and fixing bugs, so I would be able to release to the main track, not beta. However, at I/O suddenly Google announced Jetpack with migration to AndroidX and other things. I was excited by the new approach Google has taken and wanted to integrate it as soon as possible. There was a catch: when I started to migrate app to AndroidX, I\u0026rsquo;ve noticed that some libraries I\u0026rsquo;ve used started breaking the build with AndroidX, even with Jetifier enabled. After digging into the source code I\u0026rsquo;ve found that these libraries were using some components of old support libraries that were not migrated to the AndroidX or were removed or renamed. I\u0026rsquo;ve also noticed that some maintainers of the important libraries abandoned their projects. I was unable to migrate them myself because of poor code style of these libraries, as well as the general complexity of the solutions. There might have been some issues with the Jetifier itself: for example, RxPermissions library was broken by Jetifier, resulting my app to crash at runtime, and I was unable to figure out why: code seemed okay, without much weird hacks.\nAnother interesting example was with Moxy library for easier building of MVP pattern classes. Issues with AndroidX here were easy to fix, but it turned out that original developers were unable to maintain this library. It was forked by multiple people later on, and even turned into community-driven project (it started as in-house project inside one outsourcing company, but apparently the company has little interest in maintenance of the project after maintainer gave up). But it was much later than I\u0026rsquo;ve initially started migration.\nThis all was very frustrating. Amount of work required was immense: I needed to abstract out non-AndroidX dependencies to replace them, unbind Firebase logic from UI to ease migration and find replacement for non-AndroidX dependencies. All this work took more than a year with breaks, and at this time I was able to update some dependencies with their AndroidX variants. As a side effect, I decided to fix architectural inconsistencies as well and move everything to the one architectural pattern: I\u0026rsquo;ve managed to extract Firebase related code to the proper model layer via data sources and repositories, and this helped me to extract logic from presenter to the UseCase, reducing presenter size dramatically. Previously I was unable to decouple this logic properly because of composition with UI and poor Dagger setup, but with all this fixed, this presenter started to shine like a freshly painted car. It was quite satisfying to look into git diff for merge commit after I\u0026rsquo;ve finished this migration.\nI feel like I need to explain what \u0026ldquo;poor Dagger setup\u0026rdquo; means. I had some class like ApiContainer (I don\u0026rsquo;t remember exact name) where I was injecting Firebase classes in fields, and this class was injected in BasePresenter as dagger.Lazy. This sophisticated optimization coupled each presenter to the base, even if it didn\u0026rsquo;t need any of the Firebase classes, because somehow I didn\u0026rsquo;t know about constructor injection. I\u0026rsquo;ve started to inject required classes directly in the presenter where they were needed, inlining some base class methods and refactoring some logic. This allowed me to remove BasePresenter completely, as well as ApiContainer, and that alone simplified all my presenters dramatically and allowed me to see where I can do further improvements.\nState after refactoring After post-refactoring release was live, I had some time thinking what I can do next. There were quite a lot of things that bugged me, and I wanted to improve them. I decided to create GitHub Project to sort them out, as well as to test this feature. We didn\u0026rsquo;t use GitHub at work, so I was curious to learn how it works and is it any useful for project management.\nOver the years, a lack of tests became the most pain in the bum. You can definitely live happily without any test in your project, they say, but in practice I learned that the presence of tests indicates good health of the project and allows you to iterate faster, despite it takes more time to write tests initially. It was discussed many times over the years in the industry, but for me tests are the must-have in any project.\nAt that stage, most of the logic wasn\u0026rsquo;t testable due to binding of some Firebase related classes to UI just for loading a cover image from Cloud Storage. Untangling it required a few weeks of work, mostly to extract logic to appropriate layers. I chose Fernando Cejas\u0026rsquo; clean architecture approach with use cases, repositories and datasources. It looked like an over-complication at first, but I decided to use datasources to wrap Firebase calls, and put parsing and mapping in the repositories that depend on those datasources. And logic resided in the usecases named after specific action user needed to perform, like add books to \u0026ldquo;stash\u0026rdquo; (favorites list) or claim book via scanning its QR code. Keeping Firebase stuff in the datasources allowed to mock the access to it inside the tests of the repos. All these layers were covered with exhaustive tests. In the end I\u0026rsquo;ve got a fine looking presenters with nicely composed Rx chains that I was finally able to cover with tests, but realized that there was no need to cover presenters since logic was already tested, and presenters were only acting as the holders of usecases and Rx subscriptions. Also, I reduced dependency on an old RxJava adapter library for Firebase, because it was full of Java static methods, and it wasn\u0026rsquo;t completely thread-safe, so I tried to rewrite adapter functions inside my repos. Basically, wrapping Firebase stuff returned as Task into Rx Observables turned out to be the main function of the repositories. As a side effect of this, some repos ended up being quite anemic, i.e. just proxying datasource methods, like this. It\u0026rsquo;s not ideal, but these references were required by FirebaseUI\u0026rsquo;s RecyclerView adapter, so it was really necessary to expose it like this to avoid writing this adapter by myself. I was thinking about it, but decided to postpone the implementation for later due to the trickiness of the adapter.\nAs the next step, I decided to reimplement book position selection. Initially, I was resolving book position inside the special Cloud Function by the position\u0026rsquo;s name provided by the user and user\u0026rsquo;s city. After some time in production I found out that it doesn\u0026rsquo;t work most of the time, because users tend to write descriptions of the places that are not easily searchable on the map, i.e. not the names of the places, like \u0026ldquo;Stan\u0026rsquo;s Coffee shop\u0026rdquo;, but something more descriptive about the place, like \u0026ldquo;At my apartments\u0026rdquo; or \u0026ldquo;Shelf near the entrance to the library\u0026rdquo;. In addition, there were some weird crashes inside Promises of the Google Maps JS SDK. I initially wanted to add location picker, but it seemed too complex, thus I ended up implementing this weird automatic resolver. Though it was fun to work with Cloud Functions when they were just released, I figured that it was just about the time to do it properly, so I decided to remove this broken automation and implement proper location picker.\nWell, to pick location on the map, first, you need a map. As a map provider, initially I chose Mapbox: it seemed to be great alternative to the Google Maps that is more detailed and is convenient to use. I even planned to use it across the whole app, e.g. to show books on the map and show location of the particular book inside some small static map in book screen. I\u0026rsquo;ve used their SDK before in my other projects for reverse geocoding, but I have never used their maps and wanted to try. Unfortunately, it didn\u0026rsquo;t happen, and the whole Mapbox integration led to a lot of frustration.\nI\u0026rsquo;ve added Mapbox quickly, since its API was quite similar to Google Maps. In basic shape it worked fine, but issues started to arise when I started to add logic for the main feature – selecting position. Various random bugs started to appear on the map, and eventually a lot of weird crashes in native code started to happen. There was an open issue on GitHub for that, but it was quickly closed with a recommendation to use newer version. Unfortunately, newer version contained the same bug. Unfortunately, project\u0026rsquo;s repo has been moved since then, and now I cannot find the issue.\nAnother frustrating issue was in the documentation for Mapbox. Information appeared to be scattered across different pages, and it took me quite a lot of time to gather all the pieces together to understand what\u0026rsquo;s going on here at all. For example, it was quite challenging to find the description of the GeoJSON format they\u0026rsquo;re using. For some reason, it wasn\u0026rsquo;t easily discoverable through search, and there was little mention of it in docs. As far as I understood, they\u0026rsquo;ve used a lot of terminology from GeoJSON standard, without quite referencing definition of this terminology back to the standard in their docs or at least explicitly mentioning this terminology as related to GeoJSON (or I was too stupid to understand this sophisticated documentation). Plus, SDK was not quite covered with javadoc comments, only with basic ones.\nI\u0026rsquo;ve spent week or two trying to solve the ever growing amount of bugs and glitches until \u0026ldquo;Screw it!\u0026rdquo; moment. I decided not to bother with Mapbox any longer and rewrote all the code to Google Maps in a couple of hours. It even fit more nicely with Rx due to slightly more open interface, although it\u0026rsquo;s only my humble opinion. Since Maps SDK was not quite designed with regard to Rx, it required me to use regular hack with PublishSubject to get it working, and Google Maps SDK had all the necessary callbacks I needed for my business logic. Mapbox SDK required some extra config: for example, map required some extra style config, because default style wasn\u0026rsquo;t set (or wasn\u0026rsquo;t properly working in the first place), despite default style was just fine for me. Also, they had their own solution for managing location permissions that wasn\u0026rsquo;t fitting well with what I had used in the app, although I don\u0026rsquo;t remember whether it was really necessary to use their solution or not.\nLater I managed to extract map related stuff to the delegate to be able to share logic in different places. It also allowed me to decouple map from the particular UI and, potentially, try once again to switch to the newer Mapbox SDK. Because frankly they have improved their SDK\u0026rsquo;s API in newer versions, but I don\u0026rsquo;t know about the bugs.\nBuried plans Before I received DMCA takedown request, I was working on integrating analytics. At work, we use analytics extensively for each app feature, and it really helps to see which part is actually useful for our customers. It inspired me to integrate analytics further. I wasn\u0026rsquo;t aware about the privacy downsides of Google Analytics back then, I basically just wanted to learn how people use my app, so I decided to use Firebase Analytics first, keeping space for other analytics providers just for the sake of doing it. I wanted to have analytics layer decoupled from the particular analytics provider, so I was planning to have core analytics module with interfaces, and module for each provider, and register provider inside app\u0026rsquo;s Dagger module. Also, analytics module should have been used only in usecases, because I wanted to track how often each feature is used, not the user input. Screen tracking would also be useful, but some providers required Activity for it to be around.\nWhile I was researching different analytics providers, I found out that they all have different features and different API that were not quite compatible. It was hard to find good interface for major analytics providers, and most importantly, they often required Context or Activity instance, which wasn\u0026rsquo;t fitting to my app\u0026rsquo;s architecture and desired structure of the analytics module. I was trying to get some time and effort to handle this, but it was too late.\nAs another planned big thing I wanted to migrate to Jetpack ViewModel. Given the amount of views and presenters in the project, it didn\u0026rsquo;t seem as much work at the first glance, but actually it meant replacing the whole architectural pattern, which potentially can take a lot of effort. After refactoring I had clearly separated layers, but the main problem was that Rx subscriptions were stored both in presenters and in views, and that wasn\u0026rsquo;t supposed to happen with ViewModels. As far as I understood, in ViewModels it was only possible to subscribe to stuff in views (with little help from AutoDispose to make subscriptions lifecycle-aware). And handling inner subscriptions in the viewmodels seemed unreachable for me, because there were so many things I didn\u0026rsquo;t know how to do in ViewModels.\nMain reason for this migration was to avoid situation with Moxy: when it became abandoned, it blocked migration to AndroidX, and the whole confusion with multiple forks that followed didn\u0026rsquo;t make things easier. And despite it\u0026rsquo;s not clear whether Google will stick to their proposed ViewModel and MVVM architecture or create something completely different, at least it would be safer to expect them to provide some migration mechanism, or at least generous deprecation policy, as they usually do in their libraries or in Android SDK itself. In that case any change in architecture forced from outside will be easier.\nAlso, back then RxJava 3 was just around the corner, so I was planning to migrate to it as soon as everything will be ready, i.e. RxJava-based libraries I was using for UI and stuff. Mostly it would be just some grepping of the imports, but it was needed to have all the dependencies to update to RxJava 3 as well. I didn\u0026rsquo;t consider moving to coroutines due to sheer size of the RxJava-based code, it would be simply killer job to migrate to structured concurrency or even Kotlin Flow.\nConclusion I described my journey with this project that took 4 years. It was mostly fun to explore new technical things in this project, as well as working with some domain that is quite close to me. Despite takedown, I am satisfied with this journey. With all these highs and lows, I learned a lot and built a good thing for a portfolio. This project served me as an entry point to the public speaking: I\u0026rsquo;ve used it as a reference to my talk (in Russian) about Firebase, when it was quite new and fresh, and no one talked about it in local meetups. Also, it allowed me to learn how to manage complex refactorings and how to write tests for the reactive code.\nMain lesson I took from there is that copyright is quite challenging area, so it\u0026rsquo;s worth diving deeper into it before committing to the new project to avoid problems with it in the future. Check if any word you want to use in your project\u0026rsquo;s branding is someone\u0026rsquo;s copyrighted trademark.\nAlso, if you\u0026rsquo;re going to start some pet project to challenge yourself or to get to know some new and shiny technologies, it would be really useful to keep high standards of code style and architecture from the beginning. After you do initial commit, setup static analyser, enforce code style rules via git hook or some basic CI check, write tests, etc. It will allow you to enjoy working on the beautifully organized project years after its start. It may seem like a lot of hassle before actual fun, but it pays out when you will need to integrate some new feature with some new shiny library in it. And when you get stuck in your day job fiddling with some dull legacy code, you will be able to use your pet project as a breath of fresh air to keep going and reduce burnout risks (however, that\u0026rsquo;s another topic to discuss).\nPlease reach me out on Twitter if you have any questions or suggestions. Cheers!\n","permalink":"https://fobo66.dev/post/bookcrossing-mobile-postmortem/","summary":"\u003cp\u003eHello! Recently I received copyright infringement notice about my favorite pet project, \u003ca href=\"https://github.com/fobo66/BookcrossingMobile\"\u003eBookcrossing Mobile\u003c/a\u003e app, and was asked to remove the app from Play Store. On this sad note, I decided to discontinue its development completely and keep its code open and untouched. In this article, I will describe my journey with development of this project, some cool tricks I\u0026rsquo;ve learned, as well as provide some description of the project for anyone who may be interested in it. Project will rot quickly and will become mostly unusable after a short period of time (but still may serve as a reference), but my experience working on it might be useful for those who are looking to start some mobile app project alone.\u003c/p\u003e","title":"Bookcrossing Mobile: postmortem"},{"content":"Hello! For a long time I wanted to migrate my Bookcrossing Mobile app to Gradle Kotlin DSL. But I\u0026rsquo;ve used remote Groovy function for loading API keys from .properties files. Here is a gist with that function. Despite it\u0026rsquo;s a simple function, I didn\u0026rsquo;t want to write this for each of my projects, and sharing code via gist was somehow a good idea.\nI wanted to share this code in more common way, via plugin, so I decided to create my own plugin. After I was done with it, I realized that it would never work in the way I wanted it to work because of how Gradle is designed. This situation made me think about how little I know and understand about Gradle. I\u0026rsquo;ve asked colleagues, and turned out that they prefer not to touch Gradle scripts much and only vaguely understand how it works.\nOfficial docs are not clear enough and spread across different pages, and articles across the internet are often outdated. Getting the knowledge about Gradle helped me understand how Android build system works, how can we effectively configure our builds and how to effectively automate some tedious tasks, as well as debunk some myths about it. So, I decided to write this article.\nFor me to properly understand Gradle, it was helpful to really understand the concept of the build system. Historical references helped me much here, along with studying GNU make and Gradle sources. So I decided to use similar approach in this article, though I wouldn\u0026rsquo;t recommend to dive into the source code because it\u0026rsquo;s quite big.\nFirst of all, I\u0026rsquo;ll explain a little bit about what are build systems and what are they useful for, with small historical reference to GNU make as an example, then I walk you through the main features of Gradle that I found useful but unclear, providing some real-life examples.\nThis is not a promotional article, neither it\u0026rsquo;s a comprehensive analysis of the Gradle features. I will point out to some things which I find important and underrated, and describe them with some pros and cons that I see.\nTo make a most out of this article, you need to know any programming language, but familiarity with Java and Android development will help, since I will use a lot of terms related to it.\nWhat is a build system According to wiki, build automation system (or build system, for short) is \u0026ldquo;the process of automating the creation of a software build and the associated processes including: compiling computer source code into binary code, packaging binary code, and running automated tests\u0026rdquo;. This definition may not tell you enough, so let\u0026rsquo;s look into an example.\nImagine your first Hello World application, when you just started to learn your programming language. It probably was deadly simple, written in one file and executed from the command line. However, when complexity of the problem you solve grows, one file is often not enough. You may end up using multiple files, stored chaotically across the folder with your project. And when you need to use some third-party dependencies, command for compiling stuff will grow significantly, allowing pesky mistakes spill into it when someone will try to compile your project on different machine. You may end up with some .sh or .cmd files that will contain command that is required to build your project, but these scripts are not guaranteed to work on other OS or even in other folder, and you may end up spending more time fixing shell script for build than writing code.\nThis was even more painful in the late 1970s, when C programming language was on the rise. Software engineers were managing complex projects with lots of different files, and that files may require special treatment, e.g. generating some code before compilation. It was quite difficult to keep track of the files that need to be recompiled when changes occur, and no one wanted to fully recompile whole project when only one file was changed, because it can take ages. That inspired Stuart Feldman to create make, one of the first build automation systems.\nGeneral idea behind make was this: gather source code files, together with dependencies and resources, compile them and produce executable file. If some files were changed, make will recompile only those files and produce new executable. These steps can be easily applied not only to the software projects, but for anything that involves files.\nTo tell make what to do, special text file is used. It\u0026rsquo;s called Makefile and it contains a set of rules on which make will treat your software. With Makefile you can configure all the build process however you want. Wanna add static analysis before compilation, or need to convert all the images to WebP format before producing final executable file? It\u0026rsquo;s a bunch of lines of shell commands in Makefile.\nmake helps to deal with a lot of source code files in a systematic way, and you can be sure that your software will be build on different machine exactly as it\u0026rsquo;s built on yours, regardless where you put folder with your project on disk.\nAs an additional benefit, the rise of make helped create a set of conventions around project file organization that are easier for build system to process and simpler for humans to work with.\nSo, the concept of the build automation system can be summarized like this: perform commands defined in config file on the given set of files.\nWhat is special about Gradle make may look like it\u0026rsquo;s a perfect fit for the job as the general purpose build system, but why there are the hell lot of other build systems?\nWell, the common complaint on make was that it has over-complicated Makefile format, so project configuration often takes some time, and if you make some mistake in it, it\u0026rsquo;s quite difficult to properly debug it and locate the problem. In addition, make doesn\u0026rsquo;t offer any solution for managing dependencies: you need to manually maintain them in order somehow, and that often leads to heavily outdated dependencies and inability to update them, unless you keep an eye on them constantly. Gradle addresses these issues in its own fashion, and allows you to fully control your build process.\nBut with this power goes along a great amount of opaque stuff that you end up using like magic, without truly understanding why and thus limiting control and unnecessarily increasing complexity. I will uncover some of the concepts that I\u0026rsquo;ve misunderstood, but keep in mind that there is much more about the Gradle than I can cover in this article.\nFirst of all, Gradle uses a bunch of terms specific to it do define its core components, so learning their exact meaning and their connections between each other will be useful. You can check this article for the intro of the Gradle components. I will touch a few components I\u0026rsquo;ve got wrong initially, so you won\u0026rsquo;t make the same mistakes.\nCaching Important Gradle feature for Android developers is build cache support. It basically means that task won\u0026rsquo;t be executed if its input files were not changed. Caching system is robust and you can be sure that it works just fine all the time. I\u0026rsquo;ve seen some developers don\u0026rsquo;t trust Gradle caches and execute clean task on CI every build, increasing build times with no reason. For example, if you run Gradle build on different branches of your projects that have different changes in the code but with same caches, Gradle will detect changes and run tasks that have been really changed, e.g. it won\u0026rsquo;t compress resources if they are same for both branches. Gradle uses virtual file system to detect changes, and since version 6.5, even OS-level file system watching. More info here.\nDependency caching works in the similar way. For example, if your CI machine performs builds on different branches of your projects that have different version of the same library but with the same cache, builds will be executed correctly, because Gradle will download newer version and use it, and builds with older versions will use older version. Same goes for different Gradle versions. In short, Gradle caches dependencies by version.\nHowever, cleaning of caches is useful to perform once in a while to ensure health of the CI machine, so it won\u0026rsquo;t run out of disc space when you don\u0026rsquo;t expect it. For example, GitLab has support for cleaning up caches of its Docker executor and recommends to do it once per week.\nPlugins Gradle Plugin system is great, because it allows you to automate project setup of your app for production, e.g. signing, API keys, localization and everything specific to your business. However, Gradle provides a lot of different ways to setup these things, thus devs often abuse these features, which leads to tedious scripts that are hard to maintain.\nBasically, clean folder with Gradle project will contain almost no tasks, because there is no plugins applied in build.gradle file. If you want to have Java project build by Gradle, you need to apply java plugin first. However, if you start project from Intellij IDEA, you will get initial Gradle setup just after creating project and you will most likely have basic plugins for your language applied already. But default plugins are not always enough, and you may need to extend your project with extras that will suit your needs.\nBest way to add some additional steps into your build is using plugins. In Gradle terms, plugin is a collection of tasks. You can define tasks\u0026rsquo; dependencies (e.g. in what order they should run) and tasks themselves via Java, Groovy or Kotlin code. Tasks will be run in the specified order when you apply your plugin to the needed module, e.g. app in case of Android. As a bonus, you will get syntax highlighting and ability to write tests for your build logic.\nFor example, if you need to decrypt some sensitive info inside your repo before building the app to include it into app resources (e.g. API keys), you can use a plugin for that. This plugin may expose one task that will use OpenSSL to decrypt file provided to it as an input, and plugin will specify that this task will run before anything else.\nAnother example: you can set up a plugin to customize JUnit HTML reports to add screenshots of the app after failed tests, so you can see why your UI test has failed. It\u0026rsquo;s really helpful when you run tests on CI in headless mode, and in logs there is only exception log from Espresso that it failed to locate some button on the screen, and on screenshot it\u0026rsquo;s visible that all of a sudden permission dialog appeared in front of the app. True story. (There is a way to handle this in a better fashion, but this approach is still viable if you need more flexibility in customizing JUnit reports than JUnit itself provides)\nAnother benefit of using plugins is the possibility to extract their source code from the main project. It\u0026rsquo;s especially important when project is big. For this you have two options:\nUse buildSrc plugin folder. Publish plugin to Gradle Plugin repo or your own private Maven repo. buildSrc folder is a special beast. This is a default module to define any logic that you need for other modules to build. It can contain some code that will be compiled before any other module and will be available in the build scripts for modules. You can also define plugins and tasks in buildSrc\u0026rsquo;s build script. For Android, people often use buildSrc to store dependencies\u0026rsquo; versions and package IDs to use in app\u0026rsquo;s build.gradle in the nice way. One of the possible way to do this is described here. However, you can do more advanced things with buildSrc. As an example, you can setup API key loading from trusted source via buildSrc plugin and a bunch of tasks, so your keys won\u0026rsquo;t leak into git history and will be loaded automatically, without manual setup, which is particularly useful on CI. You may find buildSrc especially useful if you don\u0026rsquo;t want to fiddle with deployment of your plugin or if it\u0026rsquo;s needed only for your internal stuff.\nHowever, changes in buildSrc cause cache invalidation for any other module, so it may not be particularly suitable for build logic that change frequently or for huge multi-module projects. Also, Project Structure tool in Android Studio, as well as Dependabot, don\u0026rsquo;t recognize dependencies declared in buildSrc module, so you would need to keep an eye on the versions manually, and prepare for increased build times for your modules when you decide to update even one dependency version. But, as mentioned here, Gradle team plans to make buildSrc an included build, so it won\u0026rsquo;t invalidate caches in the future.\nConclusion We tried to understand build system in general and Gradle in particular, and looked into features of Gradle build system that are often confusing - cache and plugins system.\nFor further reading, you can check this repo with a lot of examples of Gradle tasks and plugins for Android development.\nHope you found this article useful. If you have anything to add or want to discuss some point regarding this article, feel free to open an issue on Github or reach me out in Twitter. Cheers!\n","permalink":"https://fobo66.dev/post/understanding-gradle/","summary":"\u003cp\u003eHello! For a long time I wanted to migrate my \u003ca href=\"https://github.com/fobo66/BookcrossingMobile\"\u003eBookcrossing Mobile\u003c/a\u003e app to Gradle Kotlin DSL. But I\u0026rsquo;ve used remote Groovy function for loading API keys from \u003ccode\u003e.properties\u003c/code\u003e files. Here is a \u003ca href=\"https://gist.github.com/fobo66/17d5116b5c7bccf5f28036f401f3c09d\"\u003egist\u003c/a\u003e with that function. Despite it\u0026rsquo;s a simple function, I didn\u0026rsquo;t want to write this for each of my projects, and sharing code via gist was somehow a good idea.\u003c/p\u003e\n\u003cp\u003eI wanted to share this code in more common way, via plugin, so I decided to create my own \u003ca href=\"https://github.com/fobo66/propertiesLoader\"\u003eplugin\u003c/a\u003e. After I was done with it, I realized that it would never work in the way I wanted it to work because of how Gradle is designed. This situation made me think about how little I know and understand about Gradle. I\u0026rsquo;ve asked colleagues, and turned out that they prefer not to touch Gradle scripts much and only vaguely understand how it works.\u003c/p\u003e","title":"Understanding Gradle for Android developers"},{"content":"Hello! Recently I decided to get back to my old project, Bookcrossing Mobile app, to update it to the recent SDK versions and try out new things and approaches in Android development. Luckily, it was fine now, migration went smoothly, much better than at first attempt year ago.\nHowever, there were some important points that I\u0026rsquo;ve discovered during migration, and I believe these point can simplify developer\u0026rsquo;s life in the future. Hope that they will be useful for you.\nBackground Bookcrossing Mobile app was started as a pet project of mine. Due to limited resources development went slowly and often I didn\u0026rsquo;t wanted to bother with some boring stuff and used a lot of third-party dependencies. Also, I thought back then that it\u0026rsquo;s completely OK to mix up Rx and non-Rx ways. Spoiler: it\u0026rsquo;s absolutely not.\nAll of a sudden, after a few months of Bookcrossing Mobile being in Beta release, Google introduced replacement of Support libraries with AndroidX. I\u0026rsquo;ve immediately downloaded new Android Studio and launched migration. And\u0026hellip; everything was completely broken. I\u0026rsquo;m sure some of you have seen this in your projects. For me it was frustrating experience. The only option was to wait until developers of the libraries I\u0026rsquo;ve used will upgrade to AndroidX, because I had been heavily dependant on these libraries. Even Jetifier was unable to help with the mess in third-party libraries, project didn\u0026rsquo;t even compile because of them.\nI was busy with other stuff, so I was unable to invest much resources into refactoring. Only after a year and a half I\u0026rsquo;ve finally got some time to work on this project again.\n(Un)surprisingly, little has changed in terms of AndroidX support after a year. Some libraries have migrated, but the majority of them stayed on support libraries, because they were no longer maintained.\nBiting the tongue, I\u0026rsquo;ve migrated app to AndroidX and replaced every library that hasn\u0026rsquo;t migrated. It took less time that I\u0026rsquo;ve expected, mostly because it was easy enough to replace outdated libraries with my own code.\nThis made me thinking about reasons why this happened and what are the ways to prepare\nCheck code quality of the third-party dependencies if possible Most of the pain was caused by the need of replacing abandoned third-party dependencies. My initial laziness in implementing some simple things turned against me. Some libs were easy to replace, and some require a lot of work.\nDespite the amount of stars on Github, developers can lose interest or free time to work on their projects. Or these projects were not intended to be maintainable at all: developer just wanted to fix his own problem and then decided to extract his solution to the library. This library can solve one problem, but create another problem, and developer may not be interested in solving this another problem because of various reasons. In addition, library can contain some tricky workarounds that will make it unmaintainable if something inside Android SDK will change in the future. It is especially the case e.g. for some RxJava wrappers around permissions or onActivityResult handlers. In short, any third-party dependency is a risk, and amount of stars on Github doesn\u0026rsquo;t matter here, but throwing money and resources into writing your own solution is even more expensive, so healthy compromise is needed.\nTo detect this kind of problems and reduce risks, consider checking the source code of the dependency. If it\u0026rsquo;s written in a clean way, well-understandable and doesn\u0026rsquo;t contain any tricky workarounds (like extensive usage of reflection, EventBus, etc.), then there is a high probability that it won\u0026rsquo;t be abandoned or can be maintained by the community, or that you can fork it, figure out how it works and adapt for your needs.\nOf course, there is no 100% insurance any library won\u0026rsquo;t be abandoned. But when code is good, you can maintain it by yourself (because it\u0026rsquo;s open-source) or implement similar code in your app using newer tools and in the way you need it in your app.\nUnderstand your architecture pattern and follow it everywhere Architecture is your friend, but when you violate its rules, no matter intentionally or not, it creates a tech debt that may not be easy to address in the future. And when the pile of tech debt grows, it will bury the whole project beneath its weight.\nAside pathos, architecture pattern (no matter which one in particular) gives you structure and decoupling of dependencies, so you can easily make changes in the future. But if its rules are not followed, e.g. you perform network request in presenter (or even in Activity) instead of data source or use data sources directly in the interactor instead of putting them in repository, you\u0026rsquo;ll have problems in the future, because you may not be able to easily substitute your components or fix their implementation to comply with updated requirements.\nAlso, architecture is the fundamental thing that is too costly to change, and if you don\u0026rsquo;t follow your architectural principles, it tends to be even more expensive.\nAbstract dependencies through the interface I\u0026rsquo;ve noticed that developers (including myself) often tend to use third-party libraries directly, without abstracting them through an interface specific to the given circumstances. Most of the times it\u0026rsquo;s fine, but in case of huge refactoring or breaking changes in newer version of the library, you may have to change the code in a lot of places, increasing chances to break it.\nOf course, there are cases when it\u0026rsquo;s impossible or unnecessary to abstract dependency, like in case of third-party UI components or RxJava chains. And that\u0026rsquo;s OK. But in other cases it will be more future-proof to wrap third-party components into your custom interfaces, because then you will be able to replace one dependency with another without much pain.\nThere are more benefits in this, and Uncle Bob will explain them better than me. I just wanted to highlight the most valuable point in terms of replacing broken dependencies with new stuff.\nWrite truly testable code Testability can mean a lot, but I wanted to pay attention to the one particular aspect here. Tests increase maintainability of your main code base, but they often are written in not so maintainable way. And from what I\u0026rsquo;ve seen, tests were the reason why projects have not been able to migrate to AndroidX. Which means that code under tests was not actually testable, so authors used some workarounds. And if even tests are built with crutches and duct tape, it can tell a lot about the quality of the main project.\nSignals of such faux testable code can be PowerMock or Robolectric listed in dependencies.\nWell, Robolectric can be really useful in some cases, but as any other tool, it can be abused heavily, making your tests fragile, so you\u0026rsquo;ll end up maintaining your tests more than the main codebase.\nPowerMock itself is a code smell, but if you forced to use some legacy code and you forced to test it somehow and you have no permission or no resources for refactoring, you have no other option. However, in this case it\u0026rsquo;s reasonable to explain to your management that next step in Android SDK evolution may (and probably will) break your app completely, so you\u0026rsquo;ll need to rewrite anything from scratch. Which means a lot of time and money thrown away. I was in the similar situation at my job once, so it\u0026rsquo;s unfortunately a real case.\nTry not to use Robolectric or mocking for things that are better tested as instrumented tests or Espresso UI tests, like things involving SDK classes, XmlPullParser, SQLite, etc. Nowadays it\u0026rsquo;s not a big deal to run these kind of tests on CI, and they are not so slow as before (except you\u0026rsquo;ve got a really slow CI machine), and with services like Bitrise, it can be extremely easy to setup. Mocking Android dependencies may not be good solution here, unless you abstract them properly so they wouldn\u0026rsquo;t affect your tests.\nSo, if you think that your code is good but you cannot easily cover it with tests, it may not be as good as you think. And this code will turn into nasty legacy code quicker than you can imagine.\nConclusion Follow SOLID principles from the beginning and your project will survive any Android SDK changes and you will save your mental health adapting to any changes in SDK or business needs. And if there is no architecture and tests in your project, prepare yourself for long and painful refactoring and your client for spending some time and money on this.\nHope that these quite random musings will be helpful for you. Reach me out directly in Twitter if you have any questions, happy to discuss. Cheers!\n","permalink":"https://fobo66.dev/post/migration-to-androidx-problems/","summary":"\u003cp\u003eHello! Recently I decided to get back to my old project, \u003ca href=\"https://github.com/fobo66/BookcrossingMobile\"\u003eBookcrossing Mobile\u003c/a\u003e app, to update it to the recent SDK versions and try out new things and approaches in Android development. Luckily, it was fine now, migration went smoothly, much better than at first attempt year ago.\u003c/p\u003e\n\u003cp\u003eHowever, there were some important points that I\u0026rsquo;ve discovered during migration, and I believe these point can simplify developer\u0026rsquo;s life in the future. Hope that they will be useful for you.\u003c/p\u003e","title":"Musings about quality: what I've learned after migrating my projects to AndroidX"},{"content":"Hello! Just wanted to share my quick notes from the RSConf Mobile day\u0026rsquo;s most important sessions. These notes are chaotic and quite short, but they contain some valuable insight shared by the brilliant speakers.\nWake up, Neo Talk was about common problems in software development.\nSecurity is broken: more than 1 billion people are affected. And we don\u0026rsquo;t pay much attention.\nOur apps are broken or incredibly complex or unusable.\nInclusivity is an ability of a group to include different people.\nDiversity is when we already have different people in our group.\nTo build better apps, we need a diverse team to have insights from different sides of life.\nFight for quality Break unacceptable patterns Stay hungry, stay foolish Autotests No one knows exactly how to write tests, but many people tried and failed.\nAppium sucks.\nSpeaker mentioned Kakao and Kaspresso.\nThere is also a Barista that has a few useful @Rules\nAndroidJUnitRunner isn\u0026rsquo;t configurable and breaks after first broken test.\nAndroidX Orchestrator can continue running tests, but it requires Google Play Services.\nThere are also Spoon and Composer libs for instrumentation.\nSpoon can generate beautiful HTML reports, but requires a bit of inconvenient configuration steps. Plus, it wasn\u0026rsquo;t updated for a long time, and Composer claims to replace it.\nMarathon combines all the features of Spoon, Orchestrator and Composer, but it\u0026rsquo;s slightly hard to integrate.\nTo fix flaky test, we need to add a little wait before action. Code was provided in slides.\nTo rerun each test clearly from scratch, we can use adb pm clear. But it will increase run time of the tests.\nAll of this is handled in Kaspresso library by Kaspersky Labs, partly authored by the speaker. They talked about it a lot in the past, but it\u0026rsquo;s still wasn\u0026rsquo;t released.\n","permalink":"https://fobo66.dev/post/rsconf-notes/","summary":"\u003cp\u003eHello! Just wanted to share my quick notes from the \u003ca href=\"https://rsconf.by\"\u003eRSConf Mobile day\u003c/a\u003e\u0026rsquo;s most important sessions. These notes are chaotic and quite short, but they contain some valuable insight shared by the brilliant speakers.\u003c/p\u003e\n\u003ch2 id=\"wake-up-neo\"\u003eWake up, Neo\u003c/h2\u003e\n\u003cp\u003eTalk was about common problems in software development.\u003c/p\u003e\n\u003cp\u003eSecurity is broken: more than 1 billion people are affected. And we don\u0026rsquo;t pay much attention.\u003c/p\u003e\n\u003cp\u003eOur apps are broken or incredibly complex or unusable.\u003c/p\u003e\n\u003cp\u003eInclusivity is an ability of a group to include different people.\u003c/p\u003e","title":"Notes from RSConf Mobile Day"},{"content":"Hello! In this article, I\u0026rsquo;m going to review some of the existing services that provide you the possibility to perform logging in production in a safe way. No more struggle with attaching log files to feedback and spending hours on figuring out what went wrong without any clues.\nGenerally, logging in production is considered harmful, because you can expose sensitive information from your application to hackers. It\u0026rsquo;s a serious security trait, but disabling logs in production may not be the best solution: you won\u0026rsquo;t be able to see the state of your app during investigation of the strange crashes or suspicious feedbacks coming from users. So, it\u0026rsquo;s needed to find healthy compromise between security and logging.\nIn our practice, we had incorrect server responses in app, and they were handled without exceptions, however it wasn\u0026rsquo;t intended to show this state to user. In fact, this should have prevent them from interacting with app further. During our investigation, everything seemed to be just fine. We\u0026rsquo;ve asked our support engineers to send us logs from user\u0026rsquo;s device (we had possibility to do so), and we\u0026rsquo;ve spent a lot of time figuring out what and when went wrong. We\u0026rsquo;ve finally found the root cause and fixed it, but it made us think about enabling live logging in production.\nThere are several services, and I\u0026rsquo;m going to compare them by price, ease-to-use and available features. Some services provide logging as a side feature, while most of their offered functionality is about crash handling and analytics.\nBugfender Bugfender is a dedicated service for improving quality of the apps. They provide an SDK for each major platform, not only mobile.\nBugfender, besides remote logging, allow you to collect crashes from users and see information about users\u0026rsquo; devices, so you can debug crashes with all the necessary context.\nBugfender SDK can be easily wrapped by Timber.Tree. Here is the list of all supported logging frameworks with examples. So, it\u0026rsquo;s quite easy to start using it.\nIt\u0026rsquo;s quite expensive, but there is a very limited free plan. You can check their pricing here.\nCrashlytics Crashlytics is now part of Firebase, so it\u0026rsquo;s extremely easy to integrate and use by following these instructions. Crash reporting works like a charm and helps fix crashes easily. Their SDK allow you to share logs, but you can only see logged info together with exception information. If your app uses exceptions for any incorrect situation, either fatal or non-fatal, it can be enough for you. In my case, it was not enough.\nFor convenience, you can create your own Timber.Tree to log to Crashlytics. They don\u0026rsquo;t provide it from scratch, but it\u0026rsquo;s easy to write one by yourself, just like this.\nLoggly Loggly is very expensive, but advanced service in terms of logging. They allow you to see insights in your logs and analyse them easily. There is a free plan though, but it\u0026rsquo;s very limited even for hobbyist developers, as for me. Paid plans offer integrations with various services like JIRA or Github, log history and many more, you can check here.\nThey\u0026rsquo;re providing Timber.Tree, so you basically don\u0026rsquo;t need to bother with writing your own and can start with Loggly right away. Here are the instructions for setup Loggly for Android.\nHyperlog Hyperlog is a free and open source library to send your logs to ELK or RequestBin, service for inspecting HTTP payloads.\nELK goes for Elasticsearch, Logstash, Kibana - services for storing, collecting and monitoring logs. Despite they are mostly used for web apps, we can send logs from our apps to ELK as well. Deployment and configuring of the ELK stack is out of scope of this article, but you can find details here. You can also choose one of the ELK stack providers, there are quite a lot of them.\nBasically, Hyperlog is a Timber.Tree, so you can start using it right after you get ELK stack up and running (hope it\u0026rsquo;s not your responsibility). You can find more details about HyperLog setup and usage in this amazing article.\nCustom LogService for ELK This is the most complex solution you can implement, so most of the teams will unlikely use it. However, if your organization wants to have everything onsite and don\u0026rsquo;t trust third-parties, you can go for it.\nNot only you will need ELK stack to be set up, but you will need to write your own LogService. There are plenty of existing implementations (e.g. this or this), but they are outdated. I would suggest not to use them and write your own log sender with help of WorkManager or use HyperLog.\nBasically, this approach is free of charge, if you have all the required infrastructure. You will only invest tremendous amount of time to make it work for you. So, I would recommend to consider your resources before starting deploying ELK and writing everything from scratch.\nIn this article, I\u0026rsquo;ve reviewed some remote logging solutions for Android in terms of features and price. Feel free to share and discuss it. Hope that it will be useful for you.\n","permalink":"https://fobo66.dev/post/remote-logging/","summary":"\u003cp\u003eHello! In this article, I\u0026rsquo;m going to review some of the existing services that provide you the possibility to perform logging in production in a safe way. No more struggle with attaching log files to feedback and spending hours on figuring out what went wrong without any clues.\u003c/p\u003e\n\u003cp\u003eGenerally, logging in production is considered harmful, because you can expose sensitive information from your application to hackers. It\u0026rsquo;s a serious security trait, but disabling logs in production may not be the best solution: you won\u0026rsquo;t be able to see the state of your app during investigation of the strange crashes or suspicious feedbacks coming from users. So, it\u0026rsquo;s needed to find healthy compromise between security and logging.\u003c/p\u003e","title":"Remote logging for mobile apps"},{"content":"Hello! In this article I’ll show one small trick with SQLite savepoints and my story how I’ve found this trick.\nGoogle claims that Android fully supports SQLite database engine. But the code that framework provides to us sometimes isn’t implemented well, so we cannot use some SQLite features and forced to search for workarounds.\nOne of the features that doesn’t work as expected is savepoint. In short, it’s a tag in SQLite’s operations journal that can mark some important point in operations history to fall back to it if something was wrong. It’s similar to transaction, but savepoints have one big advantage over transactions: you can have as much savepoints as you need. You can have only one transaction at the time, but when you need to undo only particular changes inside huge transaction, savepoint can be used. Actually, savepoints are transactions, but with names and with possibility to nest multiple transactions inside one.\nSavepoints may be useful for step-by-step operations that involve database and where each step can be interrupted or something can go wrong there. But even in this case you can solve this problem by state machine in code, without using database in intermediate steps.\nIf you really need to use savepoints, here is the trick: always start SQL statement for rolling back to particular savepoint with semicolon. Like this:\nsqliteDb.execSql(\u0026#34;;ROLLBACK TO savepointName;\u0026#34;); That’s it for useful tricks. If you want to read a story behind this line of code and how do we get this, keep reading.\nAt my previous job, we extensively used SQLite for offline work with data and we strictly relied on it. Unfortunately, our business logic was written in very rigid way, so we cannot easily mock database for testing. That’s why we decided to test it with real database. In order not to create clutter in the database and make tests stateless, we decided to use nested transactions, like in Postgres.\nAfter quick search we found out that savepoints are good choice for us. I wrote some simple test case with savepoints, and… it crashed. Exception was strange: it has stated that I’m trying to roll back non-existent transaction. But… there was only one root transaction that should be rolled back in JUnit’s AfterClass method. I’ve spent two days debugging until I’ve found this code in android.database.DatabaseUtils#getSqlStatementType (it’s called inside android.database.sqlite.SQLiteSession#executeSpecial for each SQL statement to find transactional operations). You can check the actual version of the code at the link, but in fact they\u0026rsquo;re determining statement type by first 3 letters of the SQL statement string. And before Android P they haven\u0026rsquo;t made any special treatment for ROLLBACK TO statement.\nBecause of this very clever code, when we try to rollback savepoint (that is done by ROLLBACK TO statement), this method decides that we are going to rollback transaction by ROLLBACK statement. It may make sense, because savepoints are not widely used and guys at Google might have easily forget about ROLLBACK TO statement, but it makes no excuses to this sophisticated approach to determine type of SQL statement by first 3 letters of SQL string. TBH, I bitterly smiled looking at that code.\nMore interesting things are coming. In search for workaround for this behavior, I’ve asked a question on StackOverflow, googled a lot until I found this issue on Android Issue Tracker. In short, this issue was opened in 2012, hanged in assigned state since then, had some comments (including one comment with possible workaround that I’ve mentioned above and patch for adding savepoint support to the framework) in 2013 until I wrote additional comment in 2017. After my comment, it was reassigned and fixed in few days with note that “fix will be available in Android P”. I understand that this issue had low priority, but… I haven’t seen such a big gap between opening an issue and resolving it before.\nThis story shows that Android framework is imperfect, especially its SQLite support. As a conclusion I would recommend you to use savepoints in Android app judiciously.\nThanks for reading! Hope it will be useful for you.\n","permalink":"https://fobo66.dev/post/sqlite-savepoints/","summary":"\u003cp\u003eHello! In this article I’ll show one small trick with SQLite savepoints and my story how I’ve found this trick.\u003c/p\u003e\n\u003cp\u003eGoogle \u003ca href=\"https://developer.android.com/reference/android/database/sqlite/package-summary\"\u003eclaims\u003c/a\u003e that Android fully supports SQLite database engine. But the code that framework provides to us sometimes isn’t implemented well, so we cannot use some SQLite features and forced to search for workarounds.\u003c/p\u003e\n\u003cp\u003eOne of the features that doesn’t work as expected is \u003ca href=\"https://sqlite.org/lang_savepoint.html\"\u003esavepoint\u003c/a\u003e. In short, it’s a tag in SQLite’s operations journal that can mark some important point in operations history to fall back to it if something was wrong. It’s similar to transaction, but savepoints have one big advantage over transactions: you can have as much savepoints as you need. You can have only one transaction at the time, but when you need to undo only particular changes inside huge transaction, savepoint can be used. Actually, savepoints are transactions, but with names and with possibility to nest multiple transactions inside one.\u003c/p\u003e","title":"Android SQLite savepoints: nested transactions and workaround"},{"content":"Hello!\nSometimes you need to work with some API that was created years ago, or contains more semantics that JSON allows. That APIs mostly created with XML. It’s pretty ugly format, hard to read by humans, but it’s still relevant, and sadly we need to know how to work with it. It’s not so obvious in Java as it appears to be. I’ve spent two days struggling figuring out why it’s not working. Internet has not so much relevant information about it, so I decided to write it myself. I’ve used it in Android project, but since Retrofit2 is a general Java library, you can use it in Java projects as well.\nDisclaimer: SimpleXML project is no longer maintained, so use this guide at your own risk. If you’re going to use Retrofit2 not on Android, you can use JAXB converter (Android doesn’t support JAXB).\nSo, let’s get started. First of all, I recommend to use SimpleXML framework to work with XML in Java, because with it you will write less code than with native instruments such as XPP or javax.xml.*. And, what is most important for us, it’s already integrated with Retrofit2, so you don’t need to include it separately in your project, just use Retrofit Converter.\nSo, our dependencies will look like this:\nimplementation \u0026#34;com.squareup.retrofit2:retrofit:$retrofitVersion\u0026#34; implementation \u0026#34;com.squareup.retrofit2:converter-simplexml:$retrofitVersion\u0026#34; If you use RxJava in your project, you can add RxJava adapter to simplify work with API:\n// RxJava 1.x implementation \u0026#34;com.squareup.retrofit2:adapter-rxjava:$retrofitVersion\u0026#34; // RxJava 2.x implementation \u0026#34;com.squareup.retrofit2:adapter-rxjava2:$retrofitVersion\u0026#34; Retrofit config may look like this (don’t forget to make it singleton or wrap with DI (Dagger or Guice)):\nServerAPI api = new Retrofit.Builder() .baseUrl(ServerAPI.ENDPOINT) .client(new OkHttpClient()) // you can customize it .addConverterFactory( SimpleXmlConverterFactory.createNonStrict( new Persister(new AnnotationStrategy()) // important part! ) .addCallAdapterFactory(RxJavaCallAdapterFactory.createWithScheduler(Schedulers.io())) // rx stuff .build().create(ServerAPI.class); Important note: to make SimpleXML work as expected, you should use AnnotationStrategy. To avoid SimpleXML crashes because of empty XML tags, you should use non-strict mode. It’s highly recommended, even if you pretty sure that your XML will never have empty tags. Shortly, you should construct converter factory like this:\nSimpleXmlConverterFactory.createNonStrict( new Persister(new AnnotationStrategy())); This approach requires additional configuration for your POJOs. Look at this example (pay attention to the comments):\n@Root(strict = false) // only works in non-strict mode public class Category { @Text(required = false) // will be null in forged POJO object if empty private String text; @Attribute(name = \u0026#34;id\u0026#34;, required = false) private int id; public Category() {} // empty constructor required } In short:\nall Root annotations should have strict parameter set to false\nall fields annotations should have required parameter set to false\nYou probably will need to perform special operations on your data while serializing/deserializing. To do this, you can use custom converter (described here).\nIf you get stuck with SimpleXML somewhere, you can always refer to official tutorial. It’s pretty big and sometimes not so clear and understandable, but it’s still the main source of info about this library.\nP.S. You also can customize OkHttp client for extended features, e.g. logging, caching headers rewrite, etc. To do this, follow this instructions.\nFurther reading:\nRetrofit tutorial\nSimpleXML website\nThanks for reading! All feedbacks are appreciated.\n","permalink":"https://fobo66.dev/post/working-with-xml-using-retrofit2/","summary":"\u003cp\u003eHello!\u003c/p\u003e\n\u003cp\u003eSometimes you need to work with some API that was created years ago, or contains more semantics that JSON allows. That APIs mostly created with XML. It’s pretty ugly format, hard to read  by humans, but it’s still relevant, and sadly we need to know how to work with it. It’s not so obvious in Java as it appears to be. I’ve spent two days struggling figuring out why it’s not working. Internet has not so much relevant information about it, so I decided to write it myself. I’ve used it in Android project, but since Retrofit2 is a general Java library, you can use it in Java projects as well.\u003c/p\u003e","title":"Working with XML using Retrofit2"},{"content":"Hi! My name is Andrei, I\u0026rsquo;m an Android Developer by craft. I write code since 2010, touched various tech stacks during this time, but stayed on Android. I participate in open source projects, and maintain some of them myself.\nIn this blog, I will focus on technical articles about stuff I encountered in my works. Hope it will be useful to people.\n","permalink":"https://fobo66.dev/about/","summary":"\u003cp\u003eHi! My name is Andrei, I\u0026rsquo;m an Android Developer by craft. I write code since 2010, touched various tech stacks during this time, but stayed on Android. I participate in open source projects, and maintain some of them myself.\u003c/p\u003e\n\u003cp\u003eIn this blog, I will focus on technical articles about stuff I encountered in my works. Hope it will be useful to people.\u003c/p\u003e","title":"About"}]