FlutterでTwitter API v2.0のFiltered Streamを使用する方法
概要
以前以下の記事でTwitter API v2.0のVolume StreamをFlutterフレームワークで簡単に使用する方法を紹介しました。
当記事ではタイトルにもあるように、FlutterでFiltered Streamを簡単に使用する方法を紹介していきます。
Filtered Streamとは
Filtered Streamは、以前別の記事で紹介したVolume Streamとよく似た、Twitter API v2.0で提供されているストリーミングエンドポイントです。
Volume Streamとの違い
しかし、Volume Streamがリアルタイムでツイートされた全てのツイートに対するストリーミングをサポートしている一方で、Filtered Streamは特定のルールを定義することで、ストリーミングで取得できるツイートをフィルタリングできる点が大きな違いになります。
この仕様により、Volume Streamでは実現できない、より特定の範囲でフィルタリングされたコンテンツへのプログラム的な操作が可能になります。
本記事を読みすすめるにあたって
- DartとFlutterに関する基本的な知識があることが望ましいです。
- 本記事ではVSCodeを使用していきます。
- Twitter APIを使用するためにBearerトークンをTwitter Developerで取得しておいてください。
参考資料
使用ライブラリ
Filtered Streamの使用方法を紹介するにあたって、以下のライブラリを使用していきます。
twitter_api_v2
- twitter_api_v2
Flutterプロジェクトの作成
まずは、空のFlutterプロジェクトを作成しましょう。先の推奨事項でも触れたように本記事ではVSCodeを使用していきますが、VSCode以外のエディタを使用している方は、使用しているエディタに合わせた作成方法を優先していただいて構いません。
ひとまず、以下のようにFlutterプロジェクトを作成しました。
Flutter: New Project
VSCodeを使用していて、Flutterの拡張機能をインストールしている場合は、コマンドパレットから簡単にFlutterの空プロジェクトを生成することができます。
テンプレート種別の選択
生成するテンプレートの種別は「Application」を選択しました。
任意のプロジェクト名を入力
プロジェクト名はひとまず「example」としましたが、任意の名前で構いません。
生成されたディレクトツリー
そして、上記の工程で生成された以下のディレクトリ構造を基本として、twitter_api_v2を使用してFiltered Streamの実装方法を紹介していきます。
.
├── LICENSE
├── README.md
├── analysis_options.yaml
├── android
│ ├── app
│ │ ├── build.gradle
│ │ └── src
│ │ ├── debug
│ │ │ └── AndroidManifest.xml
│ │ ├── main
│ │ │ ├── AndroidManifest.xml
│ │ │ ├── java
│ │ │ │ └── io
│ │ │ │ └── flutter
│ │ │ │ └── plugins
│ │ │ │ └── GeneratedPluginRegistrant.java
│ │ │ ├── kotlin
│ │ │ │ └── com
│ │ │ │ └── example
│ │ │ │ └── example
│ │ │ │ └── MainActivity.kt
│ │ │ └── res
│ │ │ ├── drawable
│ │ │ │ └── launch_background.xml
│ │ │ ├── drawable-v21
│ │ │ │ └── launch_background.xml
│ │ │ ├── mipmap-hdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-mdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── values
│ │ │ │ └── styles.xml
│ │ │ └── values-night
│ │ │ └── styles.xml
│ │ └── profile
│ │ └── AndroidManifest.xml
│ ├── build.gradle
│ ├── example_android.iml
│ ├── gradle
│ │ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
│ ├── gradle.properties
│ ├── gradlew
│ ├── gradlew.bat
│ ├── local.properties
│ └── settings.gradle
├── example.iml
├── ios
│ ├── Flutter
│ │ ├── AppFrameworkInfo.plist
│ │ ├── Debug.xcconfig
│ │ ├── Generated.xcconfig
│ │ ├── Release.xcconfig
│ │ └── flutter_export_environment.sh
│ ├── Runner
│ │ ├── AppDelegate.swift
│ │ ├── Assets.xcassets
│ │ │ ├── AppIcon.appiconset
│ │ │ │ ├── Contents.json
│ │ │ │ ├── Icon-App-1024x1024@1x.png
│ │ │ │ ├── Icon-App-20x20@1x.png
│ │ │ │ ├── Icon-App-20x20@2x.png
│ │ │ │ ├── Icon-App-20x20@3x.png
│ │ │ │ ├── Icon-App-29x29@1x.png
│ │ │ │ ├── Icon-App-29x29@2x.png
│ │ │ │ ├── Icon-App-29x29@3x.png
│ │ │ │ ├── Icon-App-40x40@1x.png
│ │ │ │ ├── Icon-App-40x40@2x.png
│ │ │ │ ├── Icon-App-40x40@3x.png
│ │ │ │ ├── Icon-App-60x60@2x.png
│ │ │ │ ├── Icon-App-60x60@3x.png
│ │ │ │ ├── Icon-App-76x76@1x.png
│ │ │ │ ├── Icon-App-76x76@2x.png
│ │ │ │ └── Icon-App-83.5x83.5@2x.png
│ │ │ └── LaunchImage.imageset
│ │ │ ├── Contents.json
│ │ │ ├── LaunchImage.png
│ │ │ ├── LaunchImage@2x.png
│ │ │ ├── LaunchImage@3x.png
│ │ │ └── README.md
│ │ ├── Base.lproj
│ │ │ ├── LaunchScreen.storyboard
│ │ │ └── Main.storyboard
│ │ ├── GeneratedPluginRegistrant.h
│ │ ├── GeneratedPluginRegistrant.m
│ │ ├── Info.plist
│ │ └── Runner-Bridging-Header.h
│ ├── Runner.xcodeproj
│ │ ├── project.pbxproj
│ │ ├── project.xcworkspace
│ │ │ ├── contents.xcworkspacedata
│ │ │ └── xcshareddata
│ │ │ ├── IDEWorkspaceChecks.plist
│ │ │ └── WorkspaceSettings.xcsettings
│ │ └── xcshareddata
│ │ └── xcschemes
│ │ └── Runner.xcscheme
│ └── Runner.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ ├── IDEWorkspaceChecks.plist
│ └── WorkspaceSettings.xcsettings
├── lib
│ └── main.dart
├── pubspec.lock
├── pubspec.yaml
└── test
└── widget_test.dart
使用するライブラリをインストールしよう
以下のコマンドを実行して使用するライブラリのインストールを行いましょう。
pubspec.yamlに定義を追加
まず、以下のコマンドでpubspec.yaml
へ依存ライブラリの定義を追加します。
flutter pub add twitter_api_v2
ライブラリのインストール
次に、以下のコマンドも実行してインストールを完了させます。
flutter pub get
インストール後の構成
ここまでの工程でプロジェクト直下にあるpubspec.yaml
に以下の定義が追加されていれば成功です。
dependencies:
flutter:
sdk: flutter
twitter_api_v2: ^4.0.0
使用するエンドポイントについて
ライブラリのインストールは先の工程で完了しましたが、実際に実装をしていく前に使用するエンドポイントに関する紹介をしておきます。
公式で提供されている仕様
Filtered Streamを使用する際には、公式のTwitter API v2.0から以下の3つのエンドポイントが提供されています。
エンドポイント | メソッド | 説明 |
---|---|---|
/2/tweets/search/stream | GET | 任意のルールでフィルタリングされたストリームに接続します。 |
/2/tweets/search/stream/rules | GET | 定義された任意のルールを取得することができます。 |
/2/tweets/search/stream/rules | POST | フィルタリングを行うルールを追加または削除することができます。 |
twitter_api_v2で提供している仕様
Filtered Streamの機能を使用していくにあたって上記のエンドポイントを使用していきますが、今回使用するライブラリであるtwitter_api_v2では、以下のマトリクスにあるメソッドでそれぞれのエンドポイントをサポートしています。
エンドポイント | クラス | メソッド | 説明 |
---|---|---|---|
GET /2/tweets/search/stream | TweetsService | connectFilteredStream | 任意のルールでフィルタリングされたストリームに接続します。 |
GET /2/tweets/search/stream/rules | TweetsService | lookupFilteringRules | 定義された任意のルールを返却します。 |
POST /2/tweets/search/stream/rules | TweetsService | createFilteringRules | フィルタリングを行うルールを追加します。 |
POST /2/tweets/search/stream/rules | TweetsService | destroyFilteringRules | フィルタリングを行うルールを削除します。 |
上記の2つのマトリクスを比較するとわかりますが、公式で提供されているPOST /2/tweets/search/stream/rules
エンドポイントを、twitter_api_v2ではエンドポイントの役割を明確にするために追加(create)と削除(destroy)に分割しています。その点が、公式で提供されているTwitter API v2.0とtwitter_api_v2の大きな違いになります。
フィルタリングルールを画面操作で追加してみよう
Filtered Streamを使用する際は、先に紹介したエンドポイントを使用して、あらかじめ一つ以上のフィルタリングルールを追加しておく必要があります。そのため、まずはフィルタリングルールを追加する処理を実装してみましょう。
lib
フォルダ直下にあるmain.dart
を以下のように修正してください。
import 'package:flutter/material.dart';
// twitter_api_v2を使用するためには以下のimportだけで大丈夫です。
import 'package:twitter_api_v2/twitter_api_v2.dart';
void main() {
runApp(MaterialApp(
home: const AddRuleView(),
theme: ThemeData(useMaterial3: true),
));
}
class AddRuleView extends StatefulWidget {
const AddRuleView({Key? key}) : super(key: key);
State<AddRuleView> createState() => _AddRuleViewState();
}
class _AddRuleViewState extends State<AddRuleView> {
final _twitter = TwitterApi(bearerToken: 'Bearerトークンを渡してください。');
final _addingRule = TextEditingController();
String _addedRule = 'NONE';
Widget build(BuildContext context) => Scaffold(
body: Padding(
padding: const EdgeInsets.all(30),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
TextField(controller: _addingRule),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () async {
// 入力されたルールを追加します。
// 追加したルールデータが返却されます。
final result =
await _twitter.tweets.createFilteringRules(
rules: [
FilteringRuleParam(value: _addingRule.text),
],
);
// 追加したルールのIDから再検索をして、
// 本当に入力したルールが登録されたのか確認してみます。
final addedRule =
await _twitter.tweets.lookupFilteringRules(
ruleIds: [result.data.first.id],
);
super.setState(() {
_addedRule = addedRule.data.first.value;
});
},
child: const Text('ルール追加'),
),
const SizedBox(height: 50),
Text(
'追加されたルール: $_addedRule',
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
],
),
),
);
}
実行する際の注意
先に実装したアプリを実行する前に、Twitter Developerで取得したBearerトークンをTwitterApiオブジェクトの引数として渡すことを忘れないでください。
final _twitter = TwitterApi(bearerToken: 'Bearerトークンを渡してください。');
Flutterアプリの実行
上記の処理にBearerトークンを設定した後に、flutter run
コマンドを使用してアプリを起動すると以下のような出力になります。
先に実装したこの画面では、画面中央に位置するテキストフィールドに入力したルールを「ルール追加」ボタンが押下された際に追加し、追加されたルールを画面下部のテキストに表示するようになっています。実際に、先に実装した処理が想定したとおりに機能するのか試してみましょう。
画面中央のテキストフィールドに任意の文字列を入力し、「ルール追加」ボタンを押下してください。
先の実装で画面下部に配置したTextウィジェットへ、テキストフィールドに入力した文字列が出力されれば成功です。
フィルタリングルールを画面操作で削除してみよう
先の実装でルールの追加を行えるようになりましたので、今回は逆に追加したルールを検索して削除する機能を実装してみましょう。
機能拡張するための準備
ここからは複数の画面を横断的に確認しやすいように、タブ形式で画面を作成していきます。そのため、まずは先に実装したAddRuleView
クラスをadd_rule_view.dart
ファイルとして分割してください。
import 'package:flutter/material.dart';
// twitter_api_v2を使用するためには以下のimportだけで大丈夫です。
import 'package:twitter_api_v2/twitter_api_v2.dart';
class AddRuleView extends StatefulWidget {
const AddRuleView({Key? key}) : super(key: key);
State<AddRuleView> createState() => _AddRuleViewState();
}
class _AddRuleViewState extends State<AddRuleView> {
final _twitter = TwitterApi(bearerToken: 'Bearerトークンを渡してください。');
final _addingRule = TextEditingController();
String _addedRule = 'NONE';
Widget build(BuildContext context) => Scaffold(
body: Padding(
padding: const EdgeInsets.all(30),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
TextField(controller: _addingRule),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () async {
// 入力されたルールを追加します。
// 追加したルールデータが返却されます。
final result =
await _twitter.tweets.createFilteringRules(
rules: [
FilteringRuleParam(value: _addingRule.text),
],
);
// 追加したルールのIDから再検索をして、
// 本当に入力したルールが登録されたのか確認してみます。
final addedRule =
await _twitter.tweets.lookupFilteringRules(
ruleIds: [result.data.first.id],
);
super.setState(() {
_addedRule = addedRule.data.first.value;
});
},
child: const Text('ルール追加'),
),
const SizedBox(height: 50),
Text(
'追加されたルール: $_addedRule',
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
],
),
),
);
}
次に、main.dart
を以下のように修正してください。
import 'package:example/add_rule_view.dart';
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
home: DefaultTabController(
length: 1,
child: Scaffold(
appBar: AppBar(
bottom: const TabBar(
tabs: [
Tab(
child: Text(
'ルール追加',
style: TextStyle(color: Colors.black),
),
),
],
),
),
body: const TabBarView(
children: [
AddRuleView(),
],
),
),
),
theme: ThemeData(useMaterial3: true),
));
}
ここまで実装が完了したらflutter run
で、実装した画面の出力を確認してみましょう。以下のように出力されれば成功です。
ルール管理画面の作成
ここまでで画面拡張をする準備ができましたので、早速追加したルールを検索して削除する機能を持つ管理画面を作成してみましょう。
manage_rule_view.dart
ファイルを新規作成し、そのファイルを以下のように修正してください。
import 'package:flutter/material.dart';
import 'package:twitter_api_v2/twitter_api_v2.dart';
class ManageRuleView extends StatefulWidget {
const ManageRuleView({Key? key}) : super(key: key);
State<ManageRuleView> createState() => _ManageRuleViewState();
}
class _ManageRuleViewState extends State<ManageRuleView> {
final _twitter = TwitterApi(bearerToken: 'Bearerトークンを渡してください。');
Widget build(BuildContext context) => Scaffold(
body: Padding(
padding: const EdgeInsets.fromLTRB(70, 30, 70, 30),
child: FutureBuilder(
// lookupFilteringRulesメソッドで追加済みのルールを検索します。
// 特定のルールIDを引数として渡さない場合は全検索になります。
future: _twitter.tweets.lookupFilteringRules(),
builder: (_, AsyncSnapshot snapshot) {
if (!snapshot.hasData) {
return const Center(child: CircularProgressIndicator());
}
final rules = snapshot.data;
return ListView.builder(
itemCount: rules.data.length,
itemBuilder: (__, int index) {
return ListTile(
title: Text(rules.data[index].value),
trailing: ElevatedButton(
onPressed: () async {
// destroyFilteringRulesメソッドにルールIDを引数として渡すことで、
// IDに紐づくルールを削除することができます。
await _twitter.tweets.destroyFilteringRules(
ruleIds: [rules.data[index].id],
);
super.setState(() {});
},
child: const Text('削除'),
),
);
},
);
},
),
),
);
}
次に、main.dart
を以下のように修正してください。
import 'package:example/add_rule_view.dart';
import 'package:example/manage_rule_view.dart';
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
home: DefaultTabController(
// タブの数を修正するのを忘れないでください。
length: 2,
child: Scaffold(
appBar: AppBar(
bottom: const TabBar(
tabs: [
Tab(
child: Text(
'ルール追加',
style: TextStyle(color: Colors.black),
),
),
// ルール管理のタブを追加します。
Tab(
child: Text(
'ルール管理',
style: TextStyle(color: Colors.black),
),
),
],
),
),
body: const TabBarView(
children: [
AddRuleView(),
// ルール管理画面を追加します。
ManageRuleView(),
],
),
),
),
theme: ThemeData(useMaterial3: true),
));
}
Filtered Streamに接続してみよう
さて、ここまででルールの追加と削除、そして参照の方法がわかりました。
次は、先の方法で追加したルールをもとにFiltered Streamに接続する方法を紹介します。
先ほどと同じ要領でfiltered_stream_view.dart
ファイルを新規作成し、以下のように修正してください。
import 'package:flutter/material.dart';
import 'package:twitter_api_v2/twitter_api_v2.dart';
class FilteredStreamView extends StatefulWidget {
const FilteredStreamView({Key? key}) : super(key: key);
State<FilteredStreamView> createState() => _FilteredStreamViewState();
}
class _FilteredStreamViewState extends State<FilteredStreamView> {
final _twitter = TwitterApi(bearerToken:'Bearerトークンを渡してください。');
final _tweets = <TweetData>[];
late Future<TwitterStreamResponse<FilteredStreamResponse>> _stream;
void initState() {
super.initState();
// 15分間に50回のリクエストのみが許可されるため、
// あらかじめStreamを取得しておきます。
_stream = _twitter.tweets.connectFilteredStream();
}
Widget build(BuildContext context) => Scaffold(
body: FutureBuilder(
future: _stream,
builder: (_, AsyncSnapshot snapshot) {
if (!snapshot.hasData) {
return const Center(child: CircularProgressIndicator());
}
final stream = snapshot.data.stream;
return StreamBuilder(
// データが流れてくるたびに再描画されます。
stream: stream,
builder: (__, AsyncSnapshot snapshot) {
if (!snapshot.hasData) {
return const Center(child: CircularProgressIndicator());
}
_tweets.add(snapshot.data.data);
return ListView.builder(
itemCount: _tweets.length,
itemBuilder: (___, int index) {
return Card(
child: ListTile(
title: Text(_tweets[index].id),
subtitle: Text(_tweets[index].text),
),
);
},
);
},
);
},
),
);
}
次に、main.dart
も以下のように修正してください。
import 'package:example/add_rule_view.dart';
import 'package:example/manage_rule_view.dart';
import 'package:flutter/material.dart';
import 'filtered_stream_view.dart';
void main() {
runApp(MaterialApp(
home: DefaultTabController(
length: 3,
child: Scaffold(
appBar: AppBar(
bottom: const TabBar(
tabs: [
Tab(
child: Text(
'ルール追加',
style: TextStyle(color: Colors.black),
),
),
Tab(
child: Text(
'ルール管理',
style: TextStyle(color: Colors.black),
),
),
Tab(
child: Text(
'ストリーム',
style: TextStyle(color: Colors.black),
),
),
],
),
),
body: const TabBarView(
children: [
AddRuleView(),
ManageRuleView(),
FilteredStreamView(),
],
),
),
),
theme: ThemeData(useMaterial3: true),
));
}
Flutterアプリの実行
ここまで修正が完了したら、先ほどまでの工程と同様にTwitterApiオブジェクトにBearerトークンを引数として渡すことを確認して、flutter run
コマンドでFlutterアプリを起動してみましょう。
ルールを追加した状態で新しく追加した「ストリーム」タブを開くと、追加したルールでフィルタリングされたツイートを取得できていることが確認できます。
最後に
ここまでの紹介で、twitter_api_v2を使用してFiltered StreamとFlutterアプリケーションを統合する方法がわかったかと思います。
しかし、この記事で紹介したFiltered Streamの仕様はほんの一部分でしかなく、より高度なフィルタリングルールを作成することができます。もちろん、twitter_api_v2もこの高度な仕様をサポートしていますので、公式で提供されているフィルタリングルールのシンタックスに関しては以下のページを参考にしてください。
また、これは将来的な話になりますが、このフィルタリングルールをより簡単に構築できる仕様をtwitter_api_v2の後のリリースでサポートしていく予定です。
この記事で作成したFlutterアプリケーションは以下のリポジトリで公開していますので、cloneして自由に使用していただいて構いません。
貢献者の募集
twitter_api_v2はオープンソースですのでどのような方でも開発に貢献することができます。開発リポジトリの公用語は英語にしていますが、日本人の方々も大歓迎ですのでお気軽にIssue
やPull Request
を作成してください。
また、このライブラリが役に立った場合にGitHub の開発リポジトリにスターを付けることや、Pub.dev でいいねを付けることもよろしくお願いします。これは twitter_api_v2
の開発コミュニティを活性化するためにとても大きな意味があります。
もしなにか疑問がある場合は開発リポジトリのディスカッションにでもスレッドを立てていただければと思います。
スポンサーの募集
オープンソース開発をサポートしてくださるスポンサーを募集しています。少額($1)からの寄付も可能ですので、以下のリンクから是非ご支援ください。
また、この記事にバッジを贈っていただくことでも支援は可能です。
Discussion