This article is part of C# Advent 2024, big thanks to Matthew Groves for organizing it again!
I must admit, I really missed Twitter. With that I mean the actual Twitter not the abomination that is "X". I was therefore rejoyed to see masses of fellow developers signing up for Bluesky. In fact, there is so many users there now, that it really feels right again - lots of interesting posts and actually useful news!
Therefore it felt natural to connect this year's C# Advent blog post with this social network. And because I love Uno Platform, why not combine both and create a very basic cross-platfrom Bluesky client?
Setup
I have started up from my common app template for Uno Platform apps, which contains just some auxiliary helpers which are not actually relevant to the logic of the application itself. It includes:
- A shell that is the "root" of each app window
- Integration of CommunityToolkit.Mvvm
- Scoped Dependency Injection that allows scoping services per application window (to allow for multiwindow apps)
- Some useful services like
IXamlRootProvider
(to get access to theXamlRoot
associated with the window) - Simple
NavigationService
to navigate between view models
These are generic features, so I will omit discussing them in this post, but for those interested, the whole source code is available on my GitHub
Talking to Bluesky API
Even the simplest of clients need to talk to the Bluesky API. I browsed the existing .NET libraries and found FishyFlip. It is in very active development and has NuGet packages published, so it felt like the ideal choice for my purposes.
Login
There are several ways to authenticate with Bluesky API. The simplest is to ask the user for their handle and an app password. App password can be generated in settings on Bluesky. Then you can pass these to the client's AuthenticateWithPasswordAsync
method:
Retrieving timeline
There are many ways you can retrieve and filter posts on Bluesky, but the very simplest is the default timeline feed. FishyFlip provides this via the GetTimelineAsync
method:
The method returns an instance of Result<T>
which has two possible forms - data (T0
) or error (T1
) . AsT0
will try to retrieve the data or end up as null
. Then the actual posts are in the Feed
property, which is a List<FeedViewPost>
.
To make things simpler, I created a FeedViewItemViewModel
record to hold the most important metadata:
Creating a text post
The very simplest possible post format is plain text. And again, FishyFlip has a pretty simple API for it:
Creating a BlueskyService
With the building blocks above we can put together a simple service that supports authentication, timeline retrieval, and posting.
You might have noticed that I used a ICredentialsService
as a dependency. This is to be able to store the user's credentials across application launches. This is not ideal, as ideally we would only store the access tokens, but in such case we would have to go with a more complex OAuth
based approach, which would be out of scope of this post. Instead, I am using PasswordVault
API in Uno Platform to store the app password in a secured location on each platform.
Note:
PasswordVault
is not yet supported on desktop targets of Uno Platform, so in those cases I am storing the credentials in plain text inApplicationData.LocalSettings
, which for demonstration purposes only and should not be done in production!
Building the UI
Uno Platform offers quite a wide variety of options for developers to choose from when building their app's presentation layer. One can go with XAML, or venture into the fluent world of C# Markup. One can also start simply with code-behind for logic, or use MVVM, or even the powerful Reactive MVUX design pattern.
To make things easy to follow for new users, I picked XAML and MVVM, but it should be quite simple to rewrite the app to use any combination of the mentioned technologies, as the UI and presentation logic are both very simple.
Uno Platform Hot Design
One of the perks of being a developer on Uno Platform team is the fact that I have early access to the new cool features like Uno Platform Studio Hot Design 😁 .
Hot Design is a one-of-a-kind in-app visual designer which runs on top of Uno Platform's Hot Reload feature and transforms the live application itself into a powerful designer surface. I have used it to design the UI of this sample application and it worked like a charm 🚀 !
If you haven't yet, sign up for the waitlist to try it out yourself!
Here is an example of Hot Design in action, setting up the placeholder of the post input field:
Login page
The login view of our app will be very simple, just a TextBox
, PasswordBox
and a login Button
:
In the code-behind, we need to implement the PasswordChanged
handler to update the view model on changes, as it is not possible to data-bind Password
:
The Login view is backed by a simple view model that uses BlueskyService
to login. In addition, it also tries to login via the cached credentials on initial navigation, to allow the user to skip the login screen:
Main page
After the user successfully logged in, we want to display the feed itself. I have used a simple ListView
to render the posts, and in the bottom added a TextBox
input, send Button
and a refresh Button
:
All of the logic then goes to the MainViewModel
which loads the feed on initial navigation and also updates it when the user makes a new post:
App in action
Here is our simple client in all its glory, running as a desktop app:
And because it is a Uno Platform app, it now runs everywhere without changes - for example on Android:
Next steps
Obviously, the resulting client leaves a lot to be desired to make it an actual Bluesky client app, including but not limited to:
- Incremental loading of posts
- Viewing reply threads
- Commenting, liking, and reposting
- Viewing and editing profile
These APIs are already available via FishyFlip, but I didn't include them in this sample.
The source code of the client is available on my GitHub.
Although this started off just as a C# Advent sample, I really enjoyed working on it and might actually turn this into a full fledged app. Would you want to help? Fork it and make a PR!