misskey_emoji 1.0.0
misskey_emoji: ^1.0.0 copied to clipboard
A Flutter library for caching and resolving Misskey custom emoji metadata with persistent Isar storage, search, and efficient retrieval mechanisms.
misskey_emoji #
A Flutter library for caching and resolving Misskey MFM (Markup For Misskey) emoji metadata with persistent storage and efficient retrieval.
Features #
- Emoji metadata caching with persistent storage using Isar database (names, URLs, attributes, etc.)
- Efficient emoji resolution and retrieval by shortcode
- In-memory and persistent catalog implementations
- Search functionality for emojis by shortcode and keywords
- Integration with Misskey API for emoji synchronization
- Cross-platform support (iOS/Android)
- Optimized for MFM (Markup For Misskey) emoji handling
- Note: Image data caching should be implemented on the application side using libraries like
cached_network_image
Installation #
Add this to your package's pubspec.yaml file:
dependencies:
misskey_emoji: ^1.0.0
Quick Start #
Basic Usage #
import 'package:misskey_emoji/misskey_emoji.dart';
import 'package:misskey_api_core/misskey_api_core.dart';
// Create HTTP client for Misskey API
final httpClient = MisskeyHttpClient(
config: MisskeyApiConfig(baseUrl: Uri.parse('https://misskey.io')),
);
// Create emoji API client
final emojiApi = MisskeyEmojiApi(httpClient);
// Create persistent catalog with Isar storage
final catalog = PersistentEmojiCatalog(
api: emojiApi,
store: IsarEmojiStore(),
);
// Sync emoji metadata from server
await catalog.sync();
// Get emoji metadata by shortcode
final emoji = await catalog.get(':custom_emoji:');
if (emoji != null) {
print('Emoji URL: ${emoji.url}');
print('Is animated: ${emoji.animated}');
}
// Search emojis
final searchResults = await EmojiSearch.search(
catalog,
query: 'smile',
options: EmojiSearchOptions(limit: 10),
);
Using Emoji Resolver #
// Create resolver for emoji resolution
final resolver = MisskeyEmojiResolver(catalog: catalog);
// Resolve emoji metadata from shortcode
final emojiImage = await resolver.resolve(':custom_emoji:');
if (emojiImage != null) {
print('Resolved emoji URL: ${emojiImage.url}');
print('Is animated: ${emojiImage.animated}');
print('Is sensitive: ${emojiImage.isSensitive}');
}
Displaying Emojis with Image Caching #
// For displaying emojis with image caching, implement on the application side
import 'package:cached_network_image/cached_network_image.dart';
Widget buildEmoji(String shortcode) {
return FutureBuilder<EmojiImage?>(
future: resolver.resolve(shortcode),
builder: (context, snapshot) {
if (snapshot.hasData && snapshot.data != null) {
return CachedNetworkImage(
imageUrl: snapshot.data!.url.toString(),
placeholder: (context, url) => CircularProgressIndicator(),
errorWidget: (context, url, error) => Icon(Icons.error),
);
}
return Icon(Icons.emoji_emotions);
},
);
}
In-Memory Catalog (for temporary usage) #
// For cases where persistent storage is not needed
final inMemoryCatalog = InMemoryEmojiCatalog(
api: emojiApi,
host: 'misskey.io',
);
await inMemoryCatalog.sync();
final emoji = await inMemoryCatalog.get(':example:');
Resource Management #
When you finish using catalogs or stores, call dispose() to release resources.
Basic cleanup #
final isar = await openEmojiIsarForServer(
Uri.parse('https://misskey.io'),
directory: '/path/to/isar',
);
final store = IsarEmojiStore(isar);
final catalog = PersistentEmojiCatalog(api: emojiApi, store: store);
try {
await catalog.sync();
final emoji = catalog.get(':custom_emoji:');
} finally {
await catalog.dispose(); // store.dispose() is called internally
await isar.close(); // close Isar explicitly when you own it
}
Error handling #
final catalog = PersistentEmojiCatalog(
api: emojiApi,
store: store,
onSyncError: (error, stackTrace) {
// Log errors for debugging or monitoring
print('Emoji sync failed: $error');
// You can also send to error tracking service
},
);
Riverpod integration examples #
Basic: Single provider with owned Isar
@riverpod
class EmojiCatalogNotifier extends _$EmojiCatalogNotifier {
@override
FutureOr<PersistentEmojiCatalog> build() async {
// Create API client
final httpClient = MisskeyHttpClient(
config: MisskeyApiConfig(baseUrl: Uri.parse('https://misskey.io')),
);
final emojiApi = MisskeyEmojiApi(httpClient);
// Create store with owned Isar instance
final appDir = await getApplicationDocumentsDirectory();
final isar = await openEmojiIsarForServer(
Uri.parse('https://misskey.io'),
directory: appDir.path,
);
final store = IsarEmojiStore(isar, ownsIsar: true);
final catalog = PersistentEmojiCatalog(
api: emojiApi,
store: store,
onSyncError: (error, stackTrace) {
debugPrint('Emoji sync failed: $error');
},
);
// Dispose resources when provider is disposed
ref.onDispose(() async {
await catalog.dispose(); // closes Isar because ownsIsar is true
});
await catalog.sync();
return catalog;
}
}
Recommended: Shared Isar instance for better resource management
// Shared Isar instance provider (reusable across multiple catalogs)
@riverpod
Future<Isar> emojiIsar(Ref ref) async {
final appDir = await getApplicationDocumentsDirectory();
final isar = await openEmojiIsarForServer(
Uri.parse('https://misskey.io'),
directory: appDir.path,
);
// Close Isar when the app is disposed
ref.onDispose(() async {
await isar.close();
});
return isar;
}
// Emoji catalog provider using shared Isar
@riverpod
class EmojiCatalogNotifier extends _$EmojiCatalogNotifier {
@override
FutureOr<PersistentEmojiCatalog> build() async {
final httpClient = MisskeyHttpClient(
config: MisskeyApiConfig(baseUrl: Uri.parse('https://misskey.io')),
);
final emojiApi = MisskeyEmojiApi(httpClient);
// Use shared Isar instance (ownsIsar: false is default)
final isar = await ref.watch(emojiIsarProvider.future);
final store = IsarEmojiStore(isar); // Isar lifecycle managed by emojiIsarProvider
final catalog = PersistentEmojiCatalog(
api: emojiApi,
store: store,
onSyncError: (error, stackTrace) {
// Send to error tracking service (e.g., Sentry, Firebase Crashlytics)
debugPrint('Emoji sync failed: $error');
},
);
ref.onDispose(() async {
await catalog.dispose(); // Only disposes catalog, Isar remains open
});
await catalog.sync();
return catalog;
}
}
// Usage in your widget
class EmojiPickerWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final catalogAsync = ref.watch(emojiCatalogNotifierProvider);
return catalogAsync.when(
data: (catalog) {
final emoji = catalog.get(':custom_emoji:');
return emoji != null ? Text('Found: ${emoji.name}') : Text('Not found');
},
loading: () => CircularProgressIndicator(),
error: (error, stack) => Text('Error: $error'),
);
}
}
API Reference #
For detailed API documentation, please refer to the documentation on pub.dev.
License #
This project is published by 司書 (LibraryLibrarian) under the 3-Clause BSD License. For details, please see the LICENSE file.
Related Links #
Japanese #
Misskey MFM(Markup For Misskey)絵文字のメタデータのキャッシュと解決を行うFlutterライブラリ。永続化ストレージと効率的な取得機能を提供します。
概要 #
- Isarデータベースを使用した絵文字メタデータの永続化キャッシュ(名前、URL、属性など)
- 効率的な絵文字解決と取得機能
- インメモリおよび永続化カタログの実装
- ショートコードとキーワードによる絵文字検索機能
- Misskey APIとの統合による絵文字同期
- MFM(Markup For Misskey)絵文字処理の最適化
- 注意: 画像データのキャッシュは
cached_network_imageなどのライブラリを使用してアプリケーション側で実装してください
導入 #
pubspec.yamlファイルに以下を追加してください:
dependencies:
misskey_emoji: ^1.0.0
利用方法 #
基本的な使用方法 #
import 'package:misskey_emoji/misskey_emoji.dart';
import 'package:misskey_api_core/misskey_api_core.dart';
// Misskey API用のHTTPクライアントを作成
final httpClient = MisskeyHttpClient(
config: MisskeyApiConfig(baseUrl: Uri.parse('https://misskey.io')),
);
// 絵文字APIクライアントを作成
final emojiApi = MisskeyEmojiApi(httpClient);
// Isarストレージを使用した永続化カタログを作成
final catalog = PersistentEmojiCatalog(
api: emojiApi,
store: IsarEmojiStore(),
);
// サーバーから絵文字メタデータを同期
await catalog.sync();
// ショートコードで絵文字メタデータを取得
final emoji = await catalog.get(':custom_emoji:');
if (emoji != null) {
print('絵文字URL: ${emoji.url}');
print('アニメーション: ${emoji.animated}');
}
// 絵文字を検索
final searchResults = await EmojiSearch.search(
catalog,
query: 'smile',
options: EmojiSearchOptions(limit: 10),
);
絵文字リゾルバーの使用 #
// 絵文字解決用のリゾルバーを作成
final resolver = MisskeyEmojiResolver(catalog: catalog);
// ショートコードから絵文字メタデータを解決
final emojiImage = await resolver.resolve(':custom_emoji:');
if (emojiImage != null) {
print('解決された絵文字URL: ${emojiImage.url}');
print('アニメーション: ${emojiImage.animated}');
print('センシティブ: ${emojiImage.isSensitive}');
}
画像キャッシュ付きの絵文字表示 #
// 画像キャッシュ付きで絵文字を表示する場合は、アプリケーション側で実装
import 'package:cached_network_image/cached_network_image.dart';
Widget buildEmoji(String shortcode) {
return FutureBuilder<EmojiImage?>(
future: resolver.resolve(shortcode),
builder: (context, snapshot) {
if (snapshot.hasData && snapshot.data != null) {
return CachedNetworkImage(
imageUrl: snapshot.data!.url.toString(),
placeholder: (context, url) => CircularProgressIndicator(),
errorWidget: (context, url, error) => Icon(Icons.error),
);
}
return Icon(Icons.emoji_emotions);
},
);
}
インメモリカタログ(一時的な使用) #
// 永続化ストレージが不要な場合
final inMemoryCatalog = InMemoryEmojiCatalog(
api: emojiApi,
host: 'misskey.io',
);
await inMemoryCatalog.sync();
final emoji = await inMemoryCatalog.get(':example:');
リソース管理 #
カタログやストアの利用後は、dispose()を呼び出してリソースを解放するようにしてください
基本的なクリーンアップ #
final isar = await openEmojiIsarForServer(
Uri.parse('https://misskey.io'),
directory: '/path/to/isar',
);
final store = IsarEmojiStore(isar);
final catalog = PersistentEmojiCatalog(api: emojiApi, store: store);
try {
await catalog.sync();
final emoji = catalog.get(':custom_emoji:');
} finally {
await catalog.dispose(); // store.dispose() が内部で呼ばれる
await isar.close(); // 所有している場合は明示的にクローズ
}
エラーハンドリング #
final catalog = PersistentEmojiCatalog(
api: emojiApi,
store: store,
onSyncError: (error, stackTrace) {
// デバッグや監視のためのエラーログ
print('絵文字同期失敗: $error');
// エラー追跡サービスに送信することも可能
},
);
Riverpodとの統合 #
単一プロバイダーでIsarを所有
@riverpod
class EmojiCatalogNotifier extends _$EmojiCatalogNotifier {
@override
FutureOr<PersistentEmojiCatalog> build() async {
// APIクライアントを作成
final httpClient = MisskeyHttpClient(
config: MisskeyApiConfig(baseUrl: Uri.parse('https://misskey.io')),
);
final emojiApi = MisskeyEmojiApi(httpClient);
// 所有権を持つIsarインスタンスでストアを作成
final appDir = await getApplicationDocumentsDirectory();
final isar = await openEmojiIsarForServer(
Uri.parse('https://misskey.io'),
directory: appDir.path,
);
final store = IsarEmojiStore(isar, ownsIsar: true);
final catalog = PersistentEmojiCatalog(
api: emojiApi,
store: store,
onSyncError: (error, stackTrace) {
debugPrint('絵文字同期失敗: $error');
},
);
// プロバイダーがdisposeされた時にリソースを解放
ref.onDispose(() async {
await catalog.dispose(); // ownsIsarがtrueならIsarもクローズされる
});
await catalog.sync();
return catalog;
}
}
Isarインスタンスを共有してリソース管理を改善(基本的にこちらを推奨)
// 共有Isarインスタンスプロバイダー(複数のカタログで再利用可能)
@riverpod
Future<Isar> emojiIsar(Ref ref) async {
final appDir = await getApplicationDocumentsDirectory();
final isar = await openEmojiIsarForServer(
Uri.parse('https://misskey.io'),
directory: appDir.path,
);
// アプリ終了時にIsarをクローズ
ref.onDispose(() async {
await isar.close();
});
return isar;
}
// 共有Isarを使用する絵文字カタログプロバイダー
@riverpod
class EmojiCatalogNotifier extends _$EmojiCatalogNotifier {
@override
FutureOr<PersistentEmojiCatalog> build() async {
final httpClient = MisskeyHttpClient(
config: MisskeyApiConfig(baseUrl: Uri.parse('https://misskey.io')),
);
final emojiApi = MisskeyEmojiApi(httpClient);
// 共有Isarインスタンスを使用(ownsIsar: falseがデフォルト)
final isar = await ref.watch(emojiIsarProvider.future);
final store = IsarEmojiStore(isar); // IsarのライフサイクルはemojiIsarProviderが管理
final catalog = PersistentEmojiCatalog(
api: emojiApi,
store: store,
onSyncError: (error, stackTrace) {
// エラー追跡サービスに送信(例: Sentry、Firebase Crashlytics)
debugPrint('絵文字同期失敗: $error');
},
);
ref.onDispose(() async {
await catalog.dispose(); // カタログのみをdispose、Isarは開いたまま
});
await catalog.sync();
return catalog;
}
}
// ウィジェットでの使用例
class EmojiPickerWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final catalogAsync = ref.watch(emojiCatalogNotifierProvider);
return catalogAsync.when(
data: (catalog) {
final emoji = catalog.get(':custom_emoji:');
return emoji != null ? Text('見つかりました: ${emoji.name}') : Text('見つかりません');
},
loading: () => CircularProgressIndicator(),
error: (error, stack) => Text('エラー: $error'),
);
}
}
APIリファレンス #
詳細なAPIドキュメントについては、pub.devのドキュメントを参照してください。
ライセンス #
このプロジェクトは司書(LibraryLibrarian)によって、3-Clause BSD Licenseの下で公開されています。詳細はLICENSEファイルをご覧ください。