Advanced usages

Create your own service

When developing one or more discord applications, you may need to communicate with various third-party services such as a Redis cache, a Rest API or a game server.

All these interactions with third party services usually require the design of methods or a class that will handle the entire interaction with the given service.

In order to easily develop your own service, the Mineral framework allows you to register your own service within its own service manager.


Create my first service

First, we will imagine creating a service responsible for interaction with a third-party API (here the placeholder).

For this example and in order not to reinvent the wheel, we will extend our Http service that the Mineral framework provides.

import 'package:mineral/api.dart';
import 'package:mineral_ioc/ioc.dart';

class PlaceholderApi extends MineralService {
  Http _http = Http(baseUrl: 'https://jsonplaceholder.typicode.com');
  
  PlaceholderApi() : super(inject: true /* or false */);
}

If you choose to auto-inject your service into the framework you simply instantiate your service without manually adding it to the service container.

In this case we want to get all ports from json placeholder API

import 'package:mineral/api.dart';
import 'package:mineral_ioc/ioc.dart';

class PlaceholderApi extends MineralService {
  // ...
  
  /// Get all posts
  Future<List<dynamic>> getPosts () async {
    final response = await _http.get(url: '/posts');
    return jsonDecode(response.body);
  }
}

In order to get good autocompletion of our code and to "standardise" the incoming data from the API, we will create a class that will serve as a model for our results.

class Post {
  int id;
  String title;
  String body;
  int? userId;
  
  Post(this.id, this.title, this.body, this.userId);
  
  factory Post.fromJsonPlaceholderApi(dynamic payload) {
    return Post(
      payload['id'],
      payload['title'],
      payload['body'],
      payload['userId'],
    );
  }
}

Now that our http Response template is created, we can modify our getPosts() method slightly.

import 'package:mineral/api.dart';
import 'package:mineral_ioc/ioc.dart';

class PlaceholderApi extends MineralService {
  // ...
  
  /// Get all posts
  Future<List<Post>> getPosts () async {
    final response = await _http.get(url: '/posts');
    final payload = jsonDecode(response.body);
    
    List<Post> posts = [];
    for (final item in payload) {
      final post = Post.fromJsonPlaceholderApi(item);
      posts.add(post);
    }
    
    return posts;
  }
}

Congratulations ! Our first service has been created ! 🎉

You have understood the principle, in fact we are not going to detail each method of CRUD because they are all very similar and do not add anything to the section.


Register our service

Your service is created and is now usable by your other classes, but one last problem remains: if we want to consume our service, we have to instantiate our class each time we need it.

In order to keep a single instance of our service, and thus make it accessible from any part of our application, we will register it within the Mineral framework container.

import 'package:mineral/core.dart';

Future<void> main() async {
  Kernel kernel = Kernel()
    ..intents.defined(all: true);
    
  // Now you can register your service like this if you haven't use auto-inject on your service
  ioc.bind(JsonPlaceholderApi());

  await kernel.init();
}

Consume our service

Now that the service we have created is registered within the Mineral framework container, we can now call it to operate.

In the following example, we will retrieve the number of posts in order to send them to an embed.

class FooCommand extends MineralCommand {
  FooCommand() {
    final command = CommandBuilder('get-all-posts', 'Get all posts from JsonPlaceholder');
    register(command);  
  }
  
  Future<void> handle (CommandInteraction interaction) async {
    final api = ioc.use<PlaceholderApi>(); // 1
    final List<Post> posts = await api.getPosts(); // 2
    
    List fields = []; // 3
    for (final post in posts.take(25).toList()) {
      fields.add(Field(
        name: post.title,
        value: post.value.substring(0, 1024), 
        inline: true,
      ));
    }
    
    final embed = EmbedBuilder( // 4
      description: 'List of all posts from JsonPlaceholder api',
      fields: fields,
      color: Colors.cyan_600,
    );
    
    await interaction.reply( // 5
      embeds: [embed],
      private: true,
    ); 
  }
}

Let's detail the steps in our example

  1. We call our JsonPlaceholderApi service from the Mineral framework container
  2. From our service, we use our previously declared method to retrieve all posts from the API
  3. We build our fields by selecting only the first 25 results as a result of the limitations of the Discord API and then retrieve a maximum of 1024 characters from the post.body as we are similarly limited to a maximum number of characters per Field
  4. We build our embed
  5. All we have to do is respond to the interaction by sending our embed to the executor of the command

Externalising a service as a package

When you develop a service, it can sometimes be interesting to reuse it across several Discord applications, so you have to duplicate your code.

As this practice goes against the DRY (Don't Repeat Yourself) principle, it is not advisable to copy and paste your code.

In order to solve this problem, you can deport your services into external packages available via a git repository or the dart pub registry.

Before we outsource anything to remote storage, we will modify what we have just created.

Create blank package

In order to create a new package, we invite you to read the related documentation.


Package structure

Now that your package is initialized, you should have a structure similar to the one below.

lib/
  json_placeholder_api.dart
pubspec.yaml

In order to best structure your application, we advise you to create an src folder within the lib folder.


Create our service

We will create a json_placeholder_service.dart file which we will consider as the "entry point" of the module.

The entry point must respect a strict structure in order to be recognised as a service package within the Mineral framework.

import 'package:mineral_package/mineral_package.dart';

class JsonPlaceholderService extends MineralPackage {
  @override
  String namespace = 'Mineral/Plugins/JsonPlaceholder';

  @override
  String label = 'JsonPlaceholder';

  @override
  String description =  'Used to call the JsonPlaceholder api.';

  Future<void> init () async {
    // Executed code when the package was loaded by the framework
  }
  
  // Your methods here
}

Next we will export our service to the entry point of the package lib/json_placeholder_api.dart.

export 'src/json_placeholder_service';

Consume our service into our Discord application

If you have published your package on the dart pub registry, you can now install it within your Discord application.

Otherwise, if you have simply published it to a repository with public visibility on a git platform, you can install it as follows.

# pubspec.yaml
dependencies:
  git:
   url: <git repository url>
   ref: <branch>

Now that your package is installed, all you have to do is add it as a module to your src/main.dart file.

Congratulations ! You have just published your first package for the Mineral framework !

You can now add your module within the src/main.dart file as shown below.

import 'package:mineral/core.dart';
import 'package:json_placeholder_api/json_placeholder_api.dart';

void main () async {
  final jsonPlaceholderService = JsonPlaceholderService();

	Kernel kernel = Kernel()
		..intents.defined(all: true)
		..modules.register([jsonPlaceholderService]);
		
	await kernel.init();
}

It is important to note that you can request parameters in your service builder to provide a customisation option for your service.

Previous