Building Lynx: Day Two
Second day building Lynx, a new mastodon client, and I’ve made some pretty good progress: I can now login to a mastodon server and fetch the home timeline.
Here is what the app looks like at the end of day two:
Double checking AI’s home work
So far I have not really incorporated AI into my daily coding task beside the occasional trip to ChatGPT when I wasn’t feeling like digging through links in Google search. Usually I get mixed results from those kind of tools so I haven’t really been spending time to really dig into them.
Today, I did try out Gemini in Android Studio beyond the autocomplete suggestion
feature I’ve opted in for a while. The task at hand was to generate data classes
based on the mastodon API documentation. For example, generating the Status
entity class based on this web
page. Genmini spit out some
boilerplate and was mostly correct but it made up some stuff that are not
present in the documentation page.
In the end, I had to go over the generated code to double check that it is actually correct. Overall estimate is that it may have saved me 10 to 15 minutes, so I’m still not convinced by the hype that those tools are pushing.
Time Sinks
Other than the tedious task of supervising AI to generate the REST API boilerplate code, I also ran into a few of your standard day to day issues that are part of the developer life.
It’s worth to be reminded of two things:
- Always read the error messages carefully and then read it again before trying a fix.
- It’s super important to write good error message, your future self will thank the present you.
A case of missing dependency
I’ve been a very happy coil user in miaocast so it was quite strange that loading image in the new app is not working. After adding some logging like the following code:
val state by painter.state.collectAsStateWithLifecycle()
when (val painterState = state) {
is AsyncImagePainter.State.Error -> {
LaunchedEffect(painterState) {
Timber.tag("NetworkImage")
.e(painterState.result.throwable, "Failed to load image %s", src)
}
// ...
}
else -> {}
}
I got a pretty strange error:
Unable to create a fetcher that supports https://…
Turns out, as part of the move to make coil multiplatform, it no longer comes with a network stack by default. To load network image, one has to add a new dependency. This is mostly my fault for not reading the doc, where this is clearly stated but it would be nice for coil to log something to the console by default to warn about this.
A case of mismatched redirect uri
Another big time sink today was to get mastodon’s oauth flow working. At first,
I missed the part where after you get the user to grant the app permission, what
you get is not the final access_token
. Instead, the app has to exchange this
code for an access_token
with another api call.
Once I figured that out, I run into another error where mastodon is saying the
code
I used to exchange for access_token
is invalid. Turns out, you must use
a redirect_uri
that is specified when you created the app with the server and
I was using the wrong value for this parameter.
In the app, I use the deeplink lynx://oauth-callback/
as the base URL for oauth
callback.
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="oauth-callback"
android:scheme="lynx" />
</intent-filter>>
At runtime, I dynamically construct the redirect URL based on which server I’m talking to, so that when the callback is made, the app can tell which server the callback is for.
// For mastodon.social, the oauth redirect uri will be
// lynx://oauth-callback/mastodon.social
fun makeRedirectUri(server: String): String =
"${AppConfiguration.OAUTH_REDIRECT_URI}/$server"
I was setting the redirect_uri
to AppConfiguration.OAUTH_REDIRECT_URI
when
I should have set it to makeRedirectUri(server)
instead. Again, this is clearly
stated in the error response (kudos to mastodon) and it just took me a while to
figure it out.
A case of missing alpha
One last issue I had was with the haze library that I use to add a blurred
background to the top bar. Somehow, I forgot that you are supposed to use a
semi-transparent color for the tint
and struggled with this for a good 10 or 15
minutes until I figured out what the issue is.
What’s Next
My main goal tomorrow is to make progress towards rendering the statuses in the timeline view, specifically:
- better date formatting
- support custom emoji in the display name
- displaying the HTML correctly
- support displaying image media attachment
- start thinking about the overall look and feel of the app
My hope is that I can reuse most of the work I did from previous attempt and spend more time in developing a visual style for the app.