Let's Post to Bluesky Social easily with Dart and Flutter
Tags: dart, flutter, bluesky, api
Published: 2023-06-15 00:54:00

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.

{% embed https://dart.dev %}
{% embed https://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.

{% embed https://pub.dev/packages/bluesky %}

Or you can see more details in in following official page.

{% embed https://atprotodart.com %}

Well, let's install this package with following commands.

**With Dart:**

```bash
dart pub add bluesky
```

```bash
dart pub get
```

**With Flutter:**

```bash
flutter pub add bluesky
```

```bash
flutter pub get
```

And let's check your `pubspec.yaml` in your Dart or Flutter app. It is successful if it looks like this and this article uses `bluesky v0.6.0`.

```yaml
name: bluesky_post
description: A sample command-line application.
version: 1.0.0
environment:
  sdk: ^3.0.0

dependencies:
  bluesky: ^0.6.0

dev_dependencies:
  lints: ^2.0.0
  test: ^1.21.0
```

## Let's Post!

The easiest way to post to Bluesky Social using the `bluesky` package is as follows. The following example simply posts any text.

```dart
import 'package:bluesky/bluesky.dart' as bsky;

Future<void> main() async {
  //! You need to be authenticated by credentials.
  final session = await bsky.createSession(
    identifier: 'HANDLE_OR_EMAIL',
    password: 'PASSWORD_OR_APP_PASSWORD',
  );

  //! Create Bluesky from session data.
  final bluesky = bsky.Bluesky.fromSession(session.data);

  final ref = await bluesky.feeds.createPost(
    text: 'Hello, Bluesky!',
  );

  //! StrongRef
  print(ref.data);
}
```

I previously wrote [an article about using the Firehose API](https://dev.to/shinyakato/easily-use-firehose-api-on-bluesky-social-with-dart-and-flutter-mdk) 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:

```dart
final session = await bsky.createSession(
  service: 'stem.social',
  identifier: 'HANDLE_OR_EMAIL',
  password: 'PASSWORD_OR_APP_PASSWORD',
);
```

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.

```dart
final bluesky = bsky.Bluesky.fromSession(session.data);
```

Okay now, if you could get session and `Bluesky` object from session, all you need to do is post any text with the following code.

```dart
final ref = await bluesky.feeds.createPost(
  text: 'Hello, Bluesky!',
);
```

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.

```dart
//! Delete any records.
await bluesky.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:

```dart
import 'dart:io';

import 'package:bluesky/bluesky.dart' as bsky;

Future<void> main() async {
  final session = await bsky.createSession(
    identifier: 'HANDLE_OR_EMAIL',
    password: 'PASSWORD_OR_APP_PASSWORD',
  );

  final bluesky = bsky.Bluesky.fromSession(session.data);

  //! Upload photo!
  final uploaded = await bluesky.repositories.uploadBlob(
    File('./funny_photo.jpg').readAsBytesSync(), // File bytes
  );

  final ref = await bluesky.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.

```dart
import 'package:bluesky/bluesky.dart' as bsky;

Future<void> main() async {
  final session = await bsky.createSession(
    identifier: 'HANDLE_OR_EMAIL',
    password: 'PASSWORD_OR_APP_PASSWORD',
  );

  final bluesky = bsky.Bluesky.fromSession(session.data);

  //! Let's use this post as quote.
  final ref = await bluesky.feeds.createPost(text: 'This will be quoted.');

  await bluesky.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.

```dart
import 'package:bluesky/bluesky.dart' as bsky;

Future<void> main() async {
  final session = await bsky.createSession(
    identifier: 'HANDLE_OR_EMAIL',
    password: 'PASSWORD_OR_APP_PASSWORD',
  );

  final bluesky = bsky.Bluesky.fromSession(session.data);

  await bluesky.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.

```dart
import 'package:bluesky/bluesky.dart' as bsky;

Future<void> main() async {
  final session = await bsky.createSession(
    identifier: 'HANDLE_OR_EMAIL',
    password: 'PASSWORD_OR_APP_PASSWORD',
  );

  final bluesky = bsky.Bluesky.fromSession(session.data);

  //! Let's reply to this post.
  final root = await bluesky.feeds.createPost(
    text: 'Reply to me',
  );

  await bluesky.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.

```dart
import 'package:bluesky/bluesky.dart' as bsky;

Future<void> main() async {
  final session = await bsky.createSession(
    identifier: 'HANDLE_OR_EMAIL',
    password: 'PASSWORD_OR_APP_PASSWORD',
  );

  final bluesky = bsky.Bluesky.fromSession(session.data);

  //! Let's reply to this post.
  final root = await bluesky.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?

```dart
import 'package:bluesky/bluesky.dart' as bsky;

Future<void> main() async {
  final session = await bsky.createSession(
    identifier: 'HANDLE_OR_EMAIL',
    password: 'PASSWORD_OR_APP_PASSWORD',
  );

  final bluesky = bsky.Bluesky.fromSession(session.data);

  final ref = await bluesky.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.

```dart
await bluesky.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.

{% embed https://pub.dev/packages/bluesky_text %}

And install it like `bluesky` package.

**With Dart:**

```bash
dart pub add bluesky_text
```

```bash
dart pub get
```

**With Flutter:**

```bash
flutter pub add bluesky_text
```

```bash
flutter pub get
```

It is successful if it looks like this in your `pubspec.yaml` and this article uses `bluesky_text v0.2.2`.

```yaml
name: bluesky_post
description: A sample command-line application.
version: 1.0.0
environment:
  sdk: ^3.0.0

dependencies:
  bluesky: ^0.6.0
  bluesky_text: ^0.2.2

dev_dependencies:
  lints: ^2.0.0
  test: ^1.21.0
```

Okay let's modify the complex **facet** setup process described earlier as follows.

```dart
import 'package:bluesky/bluesky.dart' as bsky;
import 'package:bluesky_text/bluesky_text.dart';

Future<void> main() async {
  final session = await bsky.createSession(
    identifier: 'HANDLE_OR_EMAIL',
    password: 'PASSWORD_OR_APP_PASSWORD',
  );

  final bluesky = bsky.Bluesky.fromSession(session.data);

  //! Just pass text to BlueskyText.
  final text = BlueskyText('I am @shinyakato.dev! Check https://shinyakato.dev');

  //! Extract all the mentions and hyperlinks in the text
  //! and get the facet as JSON.
  final facets = await text.entities.toFacets();

  await bluesky.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!

{% embed https://github.com/myConsciousness/atproto.dart %}

Suggestions for improvements and pull requests are also very welcome. Or you can contact me on Bluesky :)

- https://bsky.app/profile/shinyakato.dev

Thank you.