-
-
Notifications
You must be signed in to change notification settings - Fork 18
Using Custom Context with Your Bot
So we're here. We know that you've built bots, we know that you've used Context.
All those Context aware methods such as Context.reply,
Context.answerCallbackQuery, etc., made your life so easy, right? On the way,
have you ever wondered if you had the ability to use one of your classes to be
used as Context? Well, we assume you did, and that's exactly why you're here.
The custom context feature allows you to extend the base functionality of your bot by using your own custom context classes. This enables you to add tailored methods and properties, enhancing your bot's capabilities and making your code cleaner and more maintainable. This guide will walk you through the steps of creating and using custom context classes, from simple implementations to more advanced use cases with mixins.
Using a custom context provides several advantages:
- Flexibility: Define your own context classes to suit your specific needs.
- Extensibility: Easily add methods and properties to your context.
- Encapsulation: Keep your bot's logic organized and maintainable.
- Modularity: Leverage mixins to add reusable functionalities to your context.
Let's start with a simple example. We'll create a custom context class without any additional properties.
Let's start by defining a custom context class MyContext. Well, for custom
context to work with handlers that must be extended from the base Context
class.
We're building a class anyway, so let it include a method doSomething that can
perform some custom logic. Let's say our fancy hello-world printing logic is
inside :)
class MyContext extends Context {
MyContext({
required super.api,
required super.me,
required super.update,
});
void doSomething() {
// Custom logic goes here
print('Doing something!');
print('Well actually printing "Hello World" π')
}
}-
Constructor: As you can see, the extending the context class enforces our
class to have the constructor takes three required named parameters:
api,me, andupdate, which are passed to the super classContext. Namely, theapiis theRawAPIinstance,mewill be the Information about the bot from/getMerequest, and finallyupdateis the actual incoming Update. - doSomething Method: This method contains custom logic that can be executed inside a handler.
Next, we'll create a bot instance and use our custom context. As always we will
first create the bot instance. But, this time we'll specify that we look forward
to use the MyContext as our context in the type parameter.
Now that we have actually specified the type. But, when an update is received
the library does not know how to actually create an instance of the specified
class. For this reason, we have to register the constructor of our class to the
bot instance. We can do this by calling Bot.contextBuilder method. That's it.
We're set to use the custom context inside our handlers now.
// Create Bot instance along with specifiying the type parameter.
final bot = Bot<MyContext>(Platform.environment["BOT_TOKEN"]!);
// Use the custom context builder
bot.contextBuilder(MyContext.new);
// Example command handler
bot.command('start', (ctx) async {
// `ctx` will be of type MyContext :)
// Along with all the context aware methods of Context.
// So you can do ctx.reply(...);
await ctx.reply("Hello World!");
// As well as, execute custom logic inside the handler
ctx.doSomething();
});-
Bot Initialization: We create an instance of
Botwith the custom context typeMyContext. -
contextBuilder: We use the
Bot.contextBuildermethod to set the custom context constructor. -
Command Handler: Inside the command handler, we can now call the custom
method
doSomethingdefined inMyContext.
If you look closely, the Bot.contextBuilder method accepts a
ContextConstructor which is defined as follows:
typedef ContextConstructor<CTX extends Context> = FutureOr<CTX> Function({
required RawAPI api,
required User me,
required Update update,
});In other words, this means that the contextBuilder method expects a function
that takes the required parameters api, me, and update, and returns an
instance of the custom context.
Well, a side note, you can even pass a function that returns Future which resolves to the custom context class.
Now, let's create a custom context class that includes additional properties.
In this example, the custom context class MyContext has additional properties
name and age.
class MyContext extends Context {
final String name;
final int age;
MyContext({
required super.api,
required super.me,
required super.update,
required this.name,
required this.age,
});
void doSomething() {
// Custom logic using additional properties
print('Name: $name, Age: $age');
}
// Static method that returns ContextConstructor
static ContextConstructor<MyContext> create() {
return ({required api, required me, required update}) async {
// Optionally do some processing if you'd like to :)
final name = await getName(update);
final age = await getAge(update);
return MyContext(
api: api,
me: me,
update: update,
name: name,
age: age,
);
};
}
}As in the example, our MyContext class now accepts two extra properties. Well,
that makes it break the laws - we mean now if you pass MyContext.new to
Bot.contextBuilder the Dart analyzer will be pretty unhappy about your
decision. If you're wondering why this is happening, we should say you know the
reason. It's because contextBuilder takes a Function of type
ContextConstructor, and now since we have to extra properties, MyContext.new
does not aligns with the shape of ContextConstructor.
It's at this time we introduce the static method create, which can take the
extra parameters and then return the actual ContextConstructor shaped
constructor method.
So things to note:
-
Additional Properties: The
MyContextclass includes two additional properties:nameandage. - Constructor: The constructor now requires these additional properties.
- doSomething Method: This method uses the additional properties in its custom logic.
-
create Method: This static method now takes
nameandageas parameters and returns aContextConstructorforMyContext.
Next, we'll create a bot instance and use the custom context with additional properties.
// Create the Bot Instance
final bot = Bot<MyContext>(Platform.environment["BOT_TOKEN"]!);
// Use the custom context builder with additional properties
bot.contextBuilder(MyContext.create());
// Example command handler
bot.command('start', (ctx) async {
await ctx.reply("Hello World!");
// Execute custom logic inside the handler
ctx.doSomething();
});-
contextBuilder: We use the
contextBuildermethod withMyContext.createand pass the additional propertiesnameandage. -
Command Handler: Inside the command handler, we can now call the custom
method
doSomething, which utilizes the additional properties.
Mixins provide a powerful way to add reusable functionalities to your custom context. Let's see how we can leverage mixins.
Another great usage of Custom Contexts comes in to play with Mixins.
Let's take an example and define a mixin I18NMixin that provides a translate
method. For now, let's keep it simple.
mixin I18NMixin {
String translate(String key) {
// Example translation logic
Map<String, String> translations = {
'hello': 'Hola',
'world': 'Mundo',
};
return translations[key] ?? key;
}
}Next, we'll create a custom context class MyContext that uses the I18NMixin.
class MyContext extends Context with I18NMixin {
MyContext({
required super.api,
required super.me,
required super.update,
});
}Now as before we attach it with the bot and enjoy our minimal translator right inside the context handler.
Next, we'll create a bot instance and use the custom context with the mixin.
// Create the Bot Instance
final bot = Bot<MyContext>(Platform.environment["BOT_TOKEN"]!);
// Use the custom context builder
bot.contextBuilder(MyContext.new);
// Example command handler
bot.command('start', (ctx) async {
final t = ctx.translate("hello");
await ctx.reply(t);
});Custom contexts provide a flexible and powerful way to extend your bot's functionality. By defining your own context classes and leveraging mixins, you can create modular, maintainable, and reusable code tailored to your specific needs. Experiment with custom contexts and mixins to unlock the full potential of your bot!