Let's Post to Bluesky Social easily with Dart and Flutter
Shinya Kato
Posted on June 15, 2023
As already noted by many media articles, Bluesky Social is a decentralized social networking service with a Twitter-like UI. Bluesky Social is built on a next-generation common infrastructure called the AT Protocol, and the road ahead is very ambitious and full of unknown possibilities.
This article mainly shows how to post to Bluesky Social from the API in Dart language. I will not actively present the implementation in Flutter, but you can apply the same procedure in Flutter by reading this article.
What Is Dart?
For those who are not familiar with the Dart language, Dart is a relatively new programming language developed by Google. Dart has a syntax similar to Java and C, but with modern language specifications such as null safety. Above all, Flutter is probably the reason Dart is getting the most attention.
Flutter transforms the entire app development process. Build, test, and deploy beautiful mobile, web, desktop, and embedded apps from a single codebase.
flutter.dev
There is already a powerful package that makes it easy to use Bluesky Social's API with Dart and Flutter, and this article will present sample code using that package.
Install Package
To easily use Bluesky Social's API in Dart or Flutter, use the package bluesky. I develop and maintain this package and it already supports almost all endpoints, is well tested and very stable.
It is already being used in several third-party apps, and I highly recommend this package for your Bluesky Social-related apps in Dart or Flutter.
The easiest way to post to Bluesky Social using the bluesky package is as follows. The following example simply posts any text.
import'package:bluesky/bluesky.dart'asbsky;Future<void>main()async{//! You need to be authenticated by credentials.finalsession=awaitbsky.createSession(identifier:'HANDLE_OR_EMAIL',password:'PASSWORD_OR_APP_PASSWORD',);//! Create Bluesky from session data.finalbluesky=bsky.Bluesky.fromSession(session.data);finalref=awaitbluesky.feeds.createPost(text:'Hello, Bluesky!',);//! StrongRefprint(ref.data);}
I previously wrote an article about using the Firehose API with the bluesky package, which did not require credentials. However, the endpoint you will be using to post to Bluesky Social will always require your credentials.
For example, the above example uses the createSession method to authenticate with the bsky.social server using your credentials. If the authentication fails, an error is returned by bsky.social.
As a side note, if you have an account on a server other than bsky.social, you can specify any server as follows:
Then, an instance of the Bluesky object, which is required to use the bluesky package, is created using the .fromSession constructor. The .fromSession constructor can be used to create a Bluesky object from a Session object without over or under creating it.
With just this, you can send any post to Bluesky Social! The data returned from the createPost method is a reference to the actual data generated. It is represented on the AT Protocol as StrongRef and consists of an AT URI and CID link to the generated data.
Also, although not in the above code, you can delete a generated Post or other record from the AT URI as follows.
//! Delete any records.awaitbluesky.repositories.deleteRecord(uri:ref.data.uri);
Post with Images
Now that you know how to use the bluesky package to post arbitrary text to Bluesky Social, it would be useful to be able to attach images and such when actually using it.
Basically, in order to attach an image to a particular piece of content, you must first upload that image data and then POST the uploaded data in Multipart format. However, with the bluesky package, you don't have to be aware of these difficult processes at all.
Well, you can send a post with an image by doing the following:
import'dart:io';import'package:bluesky/bluesky.dart'asbsky;Future<void>main()async{finalsession=awaitbsky.createSession(identifier:'HANDLE_OR_EMAIL',password:'PASSWORD_OR_APP_PASSWORD',);finalbluesky=bsky.Bluesky.fromSession(session.data);//! Upload photo!finaluploaded=awaitbluesky.repositories.uploadBlob(File('./funny_photo.jpg').readAsBytesSync(),// File bytes);finalref=awaitbluesky.feeds.createPost(text:'Hello, Bluesky!',//! Add it.embed:bsky.Embed.images(data:bsky.EmbedImages(images:[bsky.Image(alt:'This is my funny photo!',image:uploaded.data.blob,),],),),);print(ref);}
The above example shows that it is very easy to send a post with an image.
All you have to do is upload an arbitrary image file using the uploadBlob method, and pass the uploaded Blob data to the embed parameter of createPost.
Quote the Post
You can also quote a post that has already been submitted easily as follows.
import'package:bluesky/bluesky.dart'asbsky;Future<void>main()async{finalsession=awaitbsky.createSession(identifier:'HANDLE_OR_EMAIL',password:'PASSWORD_OR_APP_PASSWORD',);finalbluesky=bsky.Bluesky.fromSession(session.data);//! Let's use this post as quote.finalref=awaitbluesky.feeds.createPost(text:'This will be quoted.');awaitbluesky.feeds.createPost(text:'Hello, Bluesky!',//! Add it.embed:bsky.Embed.record(data:bsky.EmbedRecord(ref:ref.data),),);}
Attach a Link Card to an External Site
When using Bluesky's API to embed a post with a link card from an external site, you will need to configure that link card yourself. The bluesky package makes it easy to set up.
import'package:bluesky/bluesky.dart'asbsky;Future<void>main()async{finalsession=awaitbsky.createSession(identifier:'HANDLE_OR_EMAIL',password:'PASSWORD_OR_APP_PASSWORD',);finalbluesky=bsky.Bluesky.fromSession(session.data);awaitbluesky.feeds.createPost(text:'Hello, Bluesky!',//! Add it.embed:bsky.Embed.external(data:bsky.EmbedExternal(external:bsky.EmbedExternalThumbnail(uri:'https://shinyakato.dev',title:'Shinya Kato',description:'This is my site!',),),),);}
Reply to a Specific Post
Replying to a specific post is a basic feature. Using the bluesky package, you can reply as follows.
import'package:bluesky/bluesky.dart'asbsky;Future<void>main()async{finalsession=awaitbsky.createSession(identifier:'HANDLE_OR_EMAIL',password:'PASSWORD_OR_APP_PASSWORD',);finalbluesky=bsky.Bluesky.fromSession(session.data);//! Let's reply to this post.finalroot=awaitbluesky.feeds.createPost(text:'Reply to me',);awaitbluesky.feeds.createPost(text:'Hello, Bluesky!',//! Add this.reply:bsky.ReplyRef(root:root.data,parent:root.data,),);}
With the above codes, you can easily reply to a specific post!
And you may have noticed that the ReplyRef object has two arguments, root and parent. With these arguments, you can create a thread, but if you simply want to create a series of threads, the bluesky package provides an easier way.
Let's use createThread method.
import'package:bluesky/bluesky.dart'asbsky;Future<void>main()async{finalsession=awaitbsky.createSession(identifier:'HANDLE_OR_EMAIL',password:'PASSWORD_OR_APP_PASSWORD',);finalbluesky=bsky.Bluesky.fromSession(session.data);//! Let's reply to this post.finalroot=awaitbluesky.feeds.createThread([bsky.ThreadParam(text:'First post'),bsky.ThreadParam(text:'Second post'),bsky.ThreadParam(text:'Third post'),]);//! This is the root ref from first post.print(root);}
Using createThread, it is very easy to create a series of threads.
Mentions and Links
In services similar to Bluesky, such as Twitter and Mastodon, mentions and hyperlinks in the text are automatically set up without any special attention. However, this is not the case at Bluesky.
For example, how would the status of this post appear on Bluesky's UI if the previous code were modified as follows?
import'package:bluesky/bluesky.dart'asbsky;Future<void>main()async{finalsession=awaitbsky.createSession(identifier:'HANDLE_OR_EMAIL',password:'PASSWORD_OR_APP_PASSWORD',);finalbluesky=bsky.Bluesky.fromSession(session.data);finalref=awaitbluesky.feeds.createPost(text:'I am @shinyakato.dev! Check https://shinyakato.dev',);}
The answer is, it will post the raw I am @shinyakato.dev! Check https://shinyakato.dev text with no link set in the mentions and hyperlinks.
So, if you are asking if you can't set up links in the Bluesky API, of course you can! You just need to set a special parameter called facet.
Modify the createPost method in the previous example as follows.
awaitbluesky.feeds.createPost(text:'I am @shinyakato.dev! Check https://shinyakato.dev',//! Set Facets.facets:[//! Facet for mention like "@shinyakato.dev"bsky.Facet(index:bsky.ByteSlice(byteStart:5,byteEnd:20,),features:[bsky.FacetFeature.mention(data:bsky.FacetMention(did:'did:plc:iijrtk7ocored6zuziwmqq3c',),)],),//! Facet for link like "https://shinyakato.dev"bsky.Facet(index:bsky.ByteSlice(byteStart:28,byteEnd:50,),features:[bsky.FacetFeature.link(data:bsky.FacetLink(uri:'https://shinyakato.dev',),)],),],);
Setting up this facet is one of the most difficult operations to enable mentions and hyperlinks using the Bluesky API.
The mechanics are simple, but you need to define the ByteSlice as the start and end position of the specific token you want to link to, such as a mentions or hyperlinks in the text, and even how you want the link to be placed as features.
In other words, this mechanism can be used to create a link that is completely different from the mentions or hyperlinks contained in the text.
However, I feel many people simply want mentions and hyperlinks like those offered by Twitter and Mastodon. Is there an easier solution to this complicated facet? Of course there is! But use the bluesky_text package, which is different from the bluesky package.
Let's check how to use bluesky_text in the next section!
Use bluesky_text for Easy facet Resolution
As mentioned in the previous section, activating links in text using the Bluesky API requires a facet setting, which is a somewhat difficult operation. If you need simple mentions and hyperlinks like Twitter or Mastodon, use bluesky_text.
bluesky_text is another package I am developing, designed to integrate very easily with the bluesky package.
Okay let's modify the complex facet setup process described earlier as follows.
import'package:bluesky/bluesky.dart'asbsky;import'package:bluesky_text/bluesky_text.dart';Future<void>main()async{finalsession=awaitbsky.createSession(identifier:'HANDLE_OR_EMAIL',password:'PASSWORD_OR_APP_PASSWORD',);finalbluesky=bsky.Bluesky.fromSession(session.data);//! Just pass text to BlueskyText.finaltext=BlueskyText('I am @shinyakato.dev! Check https://shinyakato.dev');//! Extract all the mentions and hyperlinks in the text//! and get the facet as JSON.finalfacets=awaittext.entities.toFacets();awaitbluesky.feeds.createPost(//! This is the text you passed to BlueskyText.text:text.value,//! Convert to Facet objects.facets:facets.map(bsky.Facet.fromJson).toList(),);}
Where the heck did the difficult facet process of a few minutes ago disappear to?! This is bluesky_text magic, all you have to do is just pass the text you want to post to the constructor when you create an instance of the BlueskyText object and call .entities.toFacet().
With just this, you can retrieve facet data contained in the text. Then, to post using the bluesky package, convert the JSON retrieved from toFacet() into a Facet object with facets.map(bsky.Facet.fromJson).toList().
Conclusion
This is how to post to Bluesky Social using the bluesky package. I also introduced a simple solution for facet, which is difficult to configure when using the Bluesky API, using the bluesky_text package.
This article mainly touches on the Dart implementation and omits the Flutter implementation, but the bluesky and bluesky_text packages can also be used in Flutter apps. The implementation method is the same as described in this article and will be easily integrated into your Flutter app!
In addition to the bluesky package and bluesky_text I have presented here, I have also developed many AT Protocol related packages for Dart/Flutter in the following monorepo. If you are interested in AT Protocol related packages for Dart/Flutter, please check it out!
This wonderful platform needs a standard and highly integrated SDK
atproto.dart provides the best development experience in such matters for Dart/Flutter devs.
2. Packages & Tools ⚒️
2.1. Dart Packages
Package
pub.dev
Docs
at_identifier: core library for the syntax in the AT Protocol standard