remove scrapped drafts [staging]

Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
This commit is contained in:
Harsh Shandilya 2021-04-01 11:55:10 +05:30
parent 49cbbd437c
commit ee341c184d
3 changed files with 0 additions and 266 deletions

View File

@ -1,85 +0,0 @@
+++
categories = ["android", "kotlin", "gradle"]
date = 2021-01-05
description = "Gradle offers buildSrc as a fantastic way of sharing common build logic across modules. Let's see how we can use it to configure our multi-module Android builds with ease."
draft = true
slug = "configuring-your-android-builds-with-buildsrc"
tags = ["buildSrc", "gradle plugin"]
title = "Configuring your Android builds with buildSrc"
+++
With the introduction of the Gradle Kotlin DSL a while ago and the more recent [performance improvements] to massively improve its compile times, it is a good time to consider using buildSrc for configuring your builds.
The simplest way to do this is through a Gradle plugin created directly in your `buildSrc` directory itself as opposed to including it from a remote repository.
## Creating your first Gradle plugin
In its most basic form, a Gradle plugin is just an implementation of the `Plugin<T>` class:
```kotlin
package com.example
import org.gradle.api.Plugin
import org.gradle.api.Project
class ExamplePlugin : Plugin<Project> {
override fun apply(target: Project) {
}
}
```
To make this `ExamplePlugin` available to your modules, register it as a plugin in your `buildSrc/build.gradle.kts`
```kotlin
gradlePlugin {
plugins {
register("example") {
id = "example-plugin"
implementationClass = "com.example.ExamplePlugin"
}
}
}
```
To use it from a module, add `example-plugin` to the `plugins` block.
```kotlin
plugins {
id("com.android.application)
kotlin("android")
`example-plugin`
}
```
To confirm it's working, add a print statement to the body of `apply` and run `./gradlew tasks`. As the build enters the configuration phase, you will see your print statement get executed.
And that's about all you need to do for getting a Gradle plugin going. Let's see how to make this plugin do some more interesting things.
## Configuring the project based on other applied plugins
In our example, we also have the `com.android.application` plugin applied. We can use that information in our custom plugin to configure our project. First, let's set up the necessary bits that let us discover the application plugin in our project.
```diff
package com.example
+import com.android.build.gradle.internal.plugins.AppPlugin
import org.gradle.api.Plugin
import org.gradle.api.Project
class ExamplePlugin : Plugin<Project> {
override fun apply(target: Project) {
+ target.plugins.all {
+ when(this) {
+ AppPlugin -> {
+ TODO("We will be doing things here when the app plugin is applied")
+ }
+ }
+ }
}
}
```
To see if this is working, try running `./gradlew tasks` again. It should cause the build to fail when configuring the app project because of the `TODO()` method.
[performance improvements]: https://docs.gradle.org/6.8/release-notes.html#performance-improvements

View File

@ -1,93 +0,0 @@
+++
categories = ["dart", "flutter", "android"]
date = 2021-02-25
description = "I've had the chance to explore Flutter (and Dart) at work for the past few weeks and I have opinions :tm:"
draft = true
slug = "my-flutter-experience"
tags = ["androiddev", "dart", "flutter", "kotlin"]
title = "My Flutter experience as an Android developer"
+++
Over the past month I've been helping Sasikanth and the rest of the engineering team at Obvious in building the mobile app for [Pause]. We chose Flutter as our framework to build it in, both out of organisational needs (wanting iOS and Android apps quickly) as well as a chance to evaluate how our teams feel about Flutter and if it's a viable option for us. It's been a fun journey so far, and there are some very pronounced differences between building native Android apps and using Flutter that were interesting to observe and adapt to.
## Hot reload is great! (when it works)
Not much to say there, it really is great! The lack of IDE support for previewing your UIs is more than made up by being able to press one key and see it on your device instead. The catch: it requires you to use a debug build which usually means janky performance (notably bad on Android, not so much on iOS). Release builds do not suffer from this, since they're compiled ahead-of-time. Ive also noticed that subsequent hot reloads seem to exacerbate the UI slowness to the point where 4-5 hot reloads in you will need to restart your app to have things move on the screen again.
However, I can say for a fact that hitting save on your code and seeing it on the device within seconds is amazing. I have also found that Flutter essentially never has build cache issues, which although rare, do still happen with native Android development.
## Widgets are straightforward to build and easy to theme
The core and material libraries included in Flutter are great for building design systems at a rapid pace, and theming has been very easy in my experience compared to Android's XML-based approach and the often confusing distinctions between _themes_ and _styles_.
The explicit distinction between stateful and stateless widgets can feel welcome to some, but after having used [Jetpack Compose] for a few months, I feel that Flutter could be doing more to handle the differences by itself rather than punt it onto users. I'm not entirely convinced that nobody's been confused between `StatefulWidget` and `StatelessWidget`, because 'state' by itself is a difficult concept to internalize without guidance.
# Documentation feels awfully lacking in core concepts
On the subject of state, Flutter has a [5 part document] that ends with a list of 12 different libraries and APIs which are your options for doing state management. That is insane! In comparison, [state in Jetpack Compose] is a detailed single page overview that explains each new term you will encounter, the core APIs for what you want to do with state, and enlists common mistakes that admittedly I too have made when I first tried Compose. These are good docs!
Flutter also glosses over side effects, which I find particularly distressing. Thinking declaratively means that individual units must be free of side effects, yet there are times when a side effect is necessary and you want a structured way of dispatching one. Flutter mentions side effects a total of two times on its website, on the [architectural overview] page, and doesn't actually explain how to dispatch one. Contrast that with the Compose [Lifecycle and side-effects] documentation which explains everything you'd want to know on the topic. Again, good docs!
Thinking in declarative and functional terms is not immediately straightforward for everyone, and while Jetpack Compose had a very clear goal of onboarding Android developers which may have driven their excellent documentation around these concepts, Flutter too needs to come to grips with the reality that a) not everyone's first foray into application programming will be Dart, and b) Developers from other platforms will have more questions than "What is the exact 1:1 mapping of everything from X to Flutter?". There's a good reason Compose docs include the '[Thinking in Compose]' page and it's about time Flutter had a documentation revamp that addresses onboarding of core concepts such as functional programming.
## Dart is...eh
:warning: Hot take alert :warning:
Dart as a language still feels like it hasn't escaped its JavaScript roots. It has a lot of rather undesirable features enabled by default, such as [implicit interfaces] and most notably: [dynamic as a fallback for type inference]. One can argue that the type system should simply fail compilation if type inference fails and ask for the user to annotate the type themselves. This is what a lot of languages do! However, Dart opted for a weird middle ground where it will sometimes generate an error and sometimes assume the type to be `dynamic`, which basically means "anything". This lack of consistency leaves the door open for a variety of subtle type related bugs that would be resolved by always throwing an error.
A lot of the Flutter APIs suffer significantly from this type unsafety. Anything that requires a `Navigator` result is implicitly typed to `dynamic`, and can fail at runtime for a variety of reasons. A simple real world example from our codebase:
```dart
final _logoutResult = await showDialog(context: context, child: LogoutDialog());
if (_logoutResult) {
_startLogout();
}
```
What you're looking at appears obvious at first glance. We expect `showDialog` to return a `bool`, and use that to determine whether or not to start the logout flow. However, there's a catch. Since `Navigator` results are untyped, you can easily encounter a situation where type inference fails at _runtime_ and trigger a crash because `_logoutResult` can no longer be coerced as a boolean value. Flutter's `AlertDialog` does not provide a way of adding a dismiss listener, so we can't actually send a value back when it is dismissed by tapping outside the view and thus `_logoutResult` became `null`. In the end, I had to opt for simply working around the type system:
```dart
final bool _logoutResult = await showDialog(context: context, child: LogoutDialog()) ?? false;
if (_logoutResult) {
_startLogout();
}
```
This specific problem has been kind of resolved by Flutter 2.0's null-safety features, which marks the result of `showDialog` as nullable and thus enforces the caller to handle the null case. However, because Flutter 2.0 is such a huge breaking change, we can't fully migrate to it until our dependencies do and that has been a challenge so far.
Dart has support for generics, but you are free to omit them when calling methods or instantiating classes and in most cases, you guessed it, Dart will pepper in `dynamic`. This to me makes generics a real [footgun] in the language.
Extension functions exist in Dart, but in my experience IDEs are unable to offer autocomplete for them. You'll first need to import the file defining your `extension` (Dart has a designated keyword for extension functions) before your IDE can autocomplete them. This sours the experience for me as a Kotlin developer, where I've come to expect my IDE to surface extension functions via completion suggestions pretty much every time.
The language also does not support [sum types], like Kotlin does with `sealed` classes, making the modeling of a variety of real-world situations slightly harder. There are community developed libraries to fill in some of these gaps ([sum_types]), but they involve codegen, which is another pain-point.
## Flutter tooling is (for the most part) great
I'll say this upfront: having the `flutter` CLI is amazing. It encapsulates a lot of functionality that is usually all over the place and puts them into a easily accessible interface. I absolutely love it.
That being said, there are some gripes I have with one specific tooling aspect: codegen. Coming from the JVM ecosystem, I have a certain degree of expectations about code generation. It should be transparent in that I should not have to explicitly run it, and the generated code must not be a part of my source tree. Flutter does both in ways that feel _wrong_ to me. Again, my distaste stems from what I've experienced in the JVM ecosystem.
To run codegen tasks in Flutter you need to call into a package called `build_runner` that performs the actual codegen. This amounts to the `flutter pub run build_runner build` command in the terminal. I have not found any support for doing this from the IDE in either of the first party plugins offered for Visual Studio Code and IntelliJ IDEA respectively. Once this is done, you'll find that your source tree is littered with `*.g.dart` files. Now you have a decision to make: whether or not to check them into version control. The official [`build_runner` docs] recommend that you **don't** do it for applications, **do** it for published libraries, and always check the documentation of the specific code generators you're using on what their authors recommend. Much more involved than the JVM answer of "no".
Switching between Flutter versions is also an experience, to say the least. `flutter upgrade` works great, but when you attempt to rollback you'll find options lacking. Why would you want to rollback? As we briefly mentioned above, [Flutter 2.0] brings significant changes both to Flutter itself and the underlying Dart language, which means that all dependencies need to be updated as well. There are ways to disable new features like sound nullsafety to allow for the upgrade to happen without waiting for dependencies, but since null safety has been such a pain point personally, I want to be able to leverage it fully when I upgrade. For rolling back, the most straightforward option is to just redownload the SDK or make a copy before updating.
## Closing notes.
While this is a rant-esque post, I do find Flutter to be a generally pleasant way to build cross-platform apps. With the web target reaching stable in [Flutter 2.0], it is now an even more lucrative option for new projects to become available on multiple platforms with a single team. However, I will still be leaning towards [Jetpack Compose] for Android-only projects. Maybe the reasons for that can form another blogpost in the future :)
[Simple]: https://simple.org
[Pause]: https://getpause.com
[Jetpack Compose]: https://d.android.com/jetpack/compose
[Implicit interfaces]: https://dart.dev/guides/language/language-tour#implicit-interfaces
[Dynamic as a fallback for type inference]: https://dart.dev/guides/language/effective-dart/design#type-inference
[Sum types]: https://chadaustin.me/2015/07/sum-types/
[sum_types]: https://pub.dev/packages/sum_types
[`build_runner` docs]: https://pub.dev/packages/build_runner#source-control
[Flutter 2.0]: https://medium.com/flutter/whats-new-in-flutter-2-0-fe8e95ecc65
[footgun]: https://en.wiktionary.org/wiki/footgun#:~:text=footgun%20(plural%20footguns),shooting%20themselves%20in%20the%20foot.
[5 part document]: https://flutter.dev/docs/development/data-and-backend/state-mgmt
[state in Jetpack Compose]: https://d.android.com/jetpack/compose/state
[architectural overview]: https://flutter.dev/docs/resources/architectural-overview
[lifecycle and side-effects]: https://d.android.com/jetpack/compose/lifecycle
[thinking in compose]: https://developer.android.com/jetpack/compose/mental-model

View File

@ -1,88 +0,0 @@
+++
categories = ["android", "kotlin"]
date = 2021-02-09
description = "Initialization scripts are a powerful Gradle feature that allow customizing the build based on the current environment"
draft = true
slug = "using-init-scripts-with-gradle"
socialImage = "/uploads/gradle-social.webp"
tags = ["gradle", "android", "kotlin", "gradle init scripts"]
title = "Init scripts in Gradle"
+++
First of all, as the [official Gradle documentation] states, "init scripts" are not the same thing as the `gradle init` command that initializes a new Gradle build in the current directory.
# What are init scripts?
They are regular build scripts seen elsewhere in Gradle, but are executed *before* the build starts. They can be used to register external listeners, add plugins to the build or specify machine-specific properties such as the location of a particular SDK required to build the project.
# Using init scripts, the official way
Gradle recommends using init scripts with the `--init-script` flag on each Gradle invocation. For example, you might want to include a couple extra tasks for yourself in a Gradle build without modifying the source of the project. To do this, define whatever you want in a separate Gradle file that will be used as an init script.
```kotlin
// init.gradle.kts
class EvaluationListenerPlugin : Plugin<Gradle> {
override fun apply(target: Gradle) {
target.projectsEvaluated {
println("Project evaluation completed")
}
}
}
apply<EvaluationListenerPlugin>()
```
Then it can be used like this
```
$ gradle --init-script init.gradle.kts
```
and somewhere in the output you'll see our "Project evaluation completed" line.
However, you can also use them to apply common configurations to *every* Gradle project on your machine, *automatically*.
# (Ab)using init scripts for fun and profit
Linux users might be familiar with the `/etc/init.d/` directory, that lets you run arbitrary scripts during the boot process. Gradle comes with a similar setup for init scripts at `$GRADLE_USER_HOME/init.d` (`GRADLE_USER_HOME` is by default the `$HOME/.gradle` directory).
So, what can you add here? Almost anything!
On my machines, I currently have two init scripts. One that automatically adds Ben Manes' [gradle-versions-plugin] (also where I first discovered init scripts!), and another that does the same for Miłosz Lewandowski's [can-i-drop-jetifier]. The scripts look something like this
```kotlin
// $HOME/.gradle/init.d/can-i-drop-jetifier.gradle.kts
initscript {
repositories {
gradlePluginPortal()
}
dependencies {
classpath("com.github.plnice:canidropjetifier:0.5")
}
}
allprojects {
apply<com.github.plnice.canidropjetifier.CanIDropJetifierPlugin>()
}
```
You'll notice that the `apply` syntax is using the actual class name rather than just the plugin name, which I believe is the only way to actually apply plugins from an init script.
# Caveats
Like everything in software, there are some problems you might run into, specifically with the `init.d`-based setup.
Since applying a plugin now requires knowing the plugin class, you'll need to do some digging into each plugin's source code that you wish to apply. A quick way to find it is searching for `Plugin<Project>` in the GitHub repository which will locate plugin classes.
If a project already has a plugin applied that you have configured in your `$GRADLE_USER_HOME/init.d` directory, it will fail the build with a classpath clash. The solution is to either rename the file in `init.d` to prevent it from being picked up by Gradle (`mv can-i-drop-jetifier.gradle can-i-drop-jetifier.gradle.disabled`), or to temporarily remove it from the project in question.
If you have an actual solution for this, please let me know on [Twitter] or post a comment below :)
[official Gradle documentation]: https://docs.gradle.org/6.8.2/userguide/init_scripts.html
[gradle-versions-plugin]: https://github.com/ben-manes/gradle-versions-plugin
[can-i-drop-jetifier]: https://github.com/plnice/can-i-drop-jetifier
[twitter]: https://twitter.com/msfjarvis