🐦

FlutterでTwitterログインを実装する

2022/12/31に公開

前提

  • Twitter DeveloperのログインAPIを使用するための設定が済んでいる。

ちょっと古めですが参考リンク
https://qiita.com/kngsym2018/items/2524d21455aac111cdee

  • Firebaseプロジェクト作成済

Firebaseの設定

  1. AuthenticationのプロバイダでTwitterを有効にし、APIキーとAPIシークレットキーをコピペする。

Twitter DeveloperにリダイレクトURIにスキーマを設定

今回は「twitterauth://」で設定します。

設定先は以下の記事が参考になります。

https://zenn.dev/soh92/articles/b56ea82d3906d6

iOS側の設定

info.plist
	<array>
		<dict>
			<key>CFBundleTypeRole</key>
			<string>Editor</string>
			<key>CFBundleURLName</key>
			<string>Bundle ID</string>
			<key>CFBundleURLSchemes</key>
			<array>
				<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
			</array>
		</dict>
		<dict>
			<key>CFBundleURLSchemes</key>
			<array>
				<string>fb$(FACEBOOK_APP_ID)</string>
				<!-- Registered Callback URLs in TwitterApp -->
				<string>twitterauth</string> // 追加
			</array>
		</dict>
	</array>

Android側の設定

android/app/src/main/AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.hogehoge">

    <uses-permission android:name="android.permission.INTERNET" />
    <queries>
        <provider android:authorities="com.facebook.katana.provider.PlatformProvider" />
    </queries>
    <application
        android:label="${appNamePrefix}アプリ名前"
        android:name="${applicationName}"
        android:icon="@mipmap/ic_launcher">
        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:launchMode="singleTop"
            android:theme="@style/LaunchTheme"
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
            android:hardwareAccelerated="true"
            android:windowSoftInputMode="adjustResize">
            <!-- Specifies an Android theme to apply to this Activity as soon as
                 the Android process has started. This theme is visible to the user
                 while the Flutter UI initializes. After that, this theme continues
                 to determine the Window background behind the Flutter UI. -->
            <meta-data
              android:name="io.flutter.embedding.android.NormalTheme"
              android:resource="@style/NormalTheme"
              />
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>

            <meta-data
                android:name="flutter_deeplinking_enabled"
                android:value="true" />

            <intent-filter android:autoVerify="true">
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
                <data
                    android:scheme="https"
                    android:host="hogehoge.page.link" />
            </intent-filter>

	        // ここから追加
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
                <!-- Registered Callback URLs in TwitterApp -->
                <data android:scheme="twitterauth" />
            </intent-filter>
	       // ここまで追加
        </activity>

        <!-- image croper start -->
        <activity
            android:name="com.yalantis.ucrop.UCropActivity"
            android:screenOrientation="portrait"
            android:theme="@style/Theme.AppCompat.Light.NoActionBar"/>
        <!-- image croper end -->
        
        <!-- Don't delete the meta-data below.
             This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
        <meta-data
            android:name="flutterEmbedding"
            android:value="2" />
        <!-- facebook login start -->
        <meta-data
            android:name="com.facebook.sdk.ApplicationId"
            android:value="@string/facebook_app_id" />
        <meta-data 
            android:name="com.facebook.sdk.ClientToken" 
            android:value="@string/facebook_client_token"/>        
        <!-- facebook login end -->
    </application>
</manifest>

Flutter側の実装

以前書いたFacebookログインの実装と概ね似たような実装です。
https://zenn.dev/ohtsuki/articles/f413bf08ce71d4

APIキーなどの読込にはdot_envを使用しています。(詳しい説明は割愛)
https://pub.dev/packages/flutter_dotenv

.env
TWITTER_API_KEY=**********************
TWITTER_SECRET_KEY=***********************
TWITTER_REDIRECT_SCHEMA=twitterauth://
twitter_auth.dart
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:twitter_login/twitter_login.dart';

import 'package:hogehoge/log.dart';
import 'package:hogehoge/models/sns_login_result.dart';

class TwitterAuth {
  TwitterAuth();

  Future<TwitterLogin> setKey() async {
    return TwitterLogin(
      apiKey: dotenv.env['TWITTER_API_KEY'] ?? '',
      apiSecretKey: dotenv.env['TWITTER_SECRET_KEY'] ?? '',
      redirectURI: dotenv.env['TWITTER_REDIRECT_SCHEMA'] ?? '',
    );
  }

  Future<SNSLoginResult?> login() async {
    try {
      final twitterAuth = await setKey();
      final authResult = await twitterAuth.login();

      if (authResult.authToken == null) {
        return null;
      }

      final twitterAuthCredential = TwitterAuthProvider.credential(
        accessToken: authResult.authToken!,
        secret: authResult.authTokenSecret!,
      );

      return SNSLoginResult(
        name: authResult.user?.name,
        credential: twitterAuthCredential,
      );
    } on Exception catch (e) {
      Log.d(e);
      rethrow;
    }
  }
}

final twitterAuthProvider = Provider<TwitterAuth>((ref) {
  return TwitterAuth();
});
login_repository.dart
class LoginRepository {
  LoginRepository(
    this._facebookAuth,
    this._firebaseManager,
    this._twitterAuth,
  );

  final FacebookAuth _facebookAuth;
  final FirebaseManager _firebaseManager;
  final TwitterManager _twitterAuth;

  // これをViewModelから呼び出す
  Future<Result<SNSLogin?>> twitterLogin() async {
    try {
      final twitterLoginResult = await _twitterAuth.login();

      if (twitterLoginResult == null) {
        // キャンセルした場合などはreturn
        return const Result.success(null);
      }

      await _firebaseManager.auth.signInWithCredential(
        twitterLoginResult.credential,
      );

      return Result.success(
        SNSLogin(name: twitterLoginResult.name),
      );
    } on Exception catch (e) {
      return Result.failure(convertError(e));
    }
  }
}

final loginRepositoryProvider = Provider<LoginRepository>((ref) {
  final facebook = ref.read(facebookAuthProvider);
  final firebase = ref.read(firebaseManagerProvider);
  final twitter = ref.read(twitterManagerProvider);
  return LoginRepository(facebook, firebase, twitter);
});

あとはLoginRepositoryからtwitterLogin()をViewModelなどで呼び出して終わりです。

Discussion