コンソールへ移動

アプリにユーザーを招待する

新しいユーザーにアプリをインストールしてもらうのに特に効果がある方法の 1 つは、ユーザーがアプリのコンテンツを友だちと共有できるようにすることです。Dynamic Links を使用することで、友だちから受け取ったおすすめコンテンツのリンクをクリックするとアプリの共有コンテンツに直接リダイレクトされるという、ユーザー間の優れた共有機能を作成できます。この機能は、ユーザーがまだアプリをインストールしておらず、まず App Store または Google Play ストアにアクセスしてアプリをインストールする必要がある場合でも利用できます。

ユーザーの紹介がもたらす定着率と Dynamic Links の持続性の組み合わせにより、新しいユーザーをアプリのコンテンツに直接誘導することや、紹介した人と紹介された人のどちらにもメリットがあるプロモーションを提供することが可能な、ユーザー間の共有機能と紹介機能を作成できます。

主なメリット

  • アプリを初めて開くユーザーは、アプリを共有した友だちが何を共有したかったかというコンテキストに基づいてカスタマイズされた体験ができます。たとえば、共有されているコンテンツを表示したり、そのユーザーを招待した友だちに直接つないだりできます。
  • 共有する相手が異なるプラットフォームを使用していたり、アプリをインストールしていない場合でも、ユーザーが友だちと簡単にコンテンツを共有できるようになります。

以下の手順に従って、この機能を導入してください。

Firebase と Dynamic Links SDK を設定する

新しい Firebase プロジェクトを設定し、アプリに Dynamic Links SDK をインストールします。

Dynamic Links SDK をインストールすると、ユーザーがアプリをインストールした後のデータを含め、Dynamic Links に関するデータを Firebase からアプリに渡せるようになります。

Dynamic Links を作成する

次は、リンクを設定して、そのリンクをユーザーが友だちに送信できるようにします。ユーザーの友だちがアプリをまだインストールしていないとしても心配する必要はありません。その場合には、Dynamic Links が自動的に対処します。

共有可能にするコンテンツの要素ごとに、Dynamic Links を作成します

Dynamic Links を作成する際は、共有するコンテンツを識別するために使用する link パラメータとして、HTTP または HTTPS URL を指定する必要があります。同等のコンテンツを表示するウェブサイトがある場合は、そのウェブサイトの URL を使用します。URL を指定することで、Dynamic Links をサポートしていないプラットフォーム(デスクトップ ブラウザなど)でもリンクが正しくレンダリングされます。次に例を示します。

https://example.page.link/?link=https://www.example.com/content?item%3D1234&apn=com.example.android&ibi=com.example.ios&isi=12345

たとえば、ゲームへの招待など、特定の個人を対象としたリンクであることを示すために、URL エンコードされたパラメータを追加することで、データ ペイロードに情報を追加することもできます。

https://example.page.link/?link=https://www.example.com/invitation?gameid%3D1234%26referrer%3D555&apn=com.example.android&ibi=com.example.ios&isi=12345

リンクを共有する前に、必要に応じて Firebase Dynamic Links URL Shortener API を使用して短く読みやすい URL を生成することもできます。短縮された Dynamic Links の例を以下に示します。

https://example.page.link/WXYZ

どちらのリンクを使用した場合でも、ユーザーが自分のデバイスで Dynamic Links を開くと、apn パラメータ(Android の場合)または ibiisi パラメータ(iOS の場合)で指定されたアプリをインストールするために、Google Play ストアまたは App Store にリダイレクトされます(インストールされていない場合)。その後、アプリをインストールして開くと、「link」パラメータで指定された URL がアプリに渡されます。

Dynamic Links を送信するための共有ボタンを追加する

まず、ハングアウトなど、チャットルームにユーザーを招待するためのリンクを生成する、ルームベースのチャットアプリの簡単な例をご覧ください。

iOS

チャットアプリのスクリーンショット 共有シート付きのチャットアプリのスクリーンショット

Android

チャットアプリのスクリーンショット 共有シート付きのチャットアプリのスクリーンショット

Swift

func generateContentLink() -> URL {
  let baseURL = URL(string: "https://your-custom-name.page.link")!
  let domain = "https://your-app.page.link"
  let linkBuilder = DynamicLinkComponents(link: baseURL, domainURIPrefix: domain)
  linkBuilder?.iOSParameters = DynamicLinkIOSParameters(bundleID: "com.your.bundleID")
  linkBuilder?.androidParameters =
      DynamicLinkAndroidParameters(packageName: "com.your.packageName")

  // Fall back to the base url if we can't generate a dynamic link.
  return linkBuilder?.link ?? baseURL
}

Objective-C

- (NSURL *)generateContentLink {
  NSURL *baseURL = [NSURL URLWithString:@"https://your-custom-name.page.link"];
  NSString *domain = @"https://your-app.page.link";
  FIRDynamicLinkComponents *builder = [[FIRDynamicLinkComponents alloc] initWithLink:baseURL domainURIPrefix:domain];
  builder.iOSParameters = [FIRDynamicLinkIOSParameters parametersWithBundleID:@"com.your.bundleID"];
  builder.androidParameters = [FIRDynamicLinkAndroidParameters parametersWithPackageName:@"com.your.packageName"];

  // Fall back to the base url if we can't generate a dynamic link.
  return builder.link ?: baseURL;
}

Java

public static Uri generateContentLink() {
    Uri baseUrl = Uri.parse("https://your-custom-name.page.link");
    String domain = "https://your-app.page.link";

    DynamicLink link = FirebaseDynamicLinks.getInstance()
            .createDynamicLink()
            .setLink(baseUrl)
            .setDomainUriPrefix(domain)
            .setIosParameters(new DynamicLink.IosParameters.Builder("com.your.bundleid").build())
            .setAndroidParameters(new DynamicLink.AndroidParameters.Builder("com.your.packageName").build())
            .buildDynamicLink();

    return link.getUri();
}

Kotlin

fun generateContentLink(): Uri {
    val baseUrl = Uri.parse("https://your-custom-name.page.link")
    val domain = "https://your-app.page.link"

    val link = FirebaseDynamicLinks.getInstance()
            .createDynamicLink()
            .setLink(baseUrl)
            .setDomainUriPrefix(domain)
            .setIosParameters(DynamicLink.IosParameters.Builder("com.your.bundleid").build())
            .setAndroidParameters(DynamicLink.AndroidParameters.Builder("com.your.packageName").build())
            .buildDynamicLink()

    return link.uri
}

Dynamic Links を作成したら、UI に共有ボタンを追加して、標準のプラットフォーム共有フローを起動します。

Swift

lazy private var shareController: UIActivityViewController = {
  let activities: [Any] = [
    "Learn how to share content via Firebase",
    URL(string: "https://firebase.google.com")!
  ]
  let controller = UIActivityViewController(activityItems: activities,
                                            applicationActivities: nil)
  return controller
}()

@IBAction func shareButtonPressed(_ sender: Any) {
  let inviteController = UIStoryboard(name: "Main", bundle: nil)
    .instantiateViewController(withIdentifier: "InviteViewController")
  self.navigationController?.pushViewController(inviteController, animated: true)
}

Objective-C

- (UIActivityViewController *)shareController {
  if (_shareController == nil) {
    NSArray *activities = @[
      @"Learn how to share content via Firebase",
      [NSURL URLWithString:@"https://firebase.google.com"]
    ];
    UIActivityViewController *controller = [[UIActivityViewController alloc] initWithActivityItems:activities applicationActivities:nil];
    _shareController = controller;
  }
  return _shareController;
}

- (IBAction)shareLinkButtonPressed:(UIView *)sender {
  if (![sender isKindOfClass:[UIView class]]) {
    return;
  }

  self.shareController.popoverPresentationController.sourceView = sender;
  [self presentViewController:self.shareController animated:YES completion:nil];
}

Java

private void onShareClicked() {
    Uri link = DynamicLinksUtil.generateContentLink();

    Intent intent = new Intent(Intent.ACTION_SEND);
    intent.setType("text/plain");
    intent.putExtra(Intent.EXTRA_TEXT, link.toString());

    startActivity(Intent.createChooser(intent, "Share Link"));
}

Kotlin

private fun onShareClicked() {
    val link = DynamicLinksUtil.generateContentLink()

    val intent = Intent(Intent.ACTION_SEND)
    intent.type = "text/plain"
    intent.putExtra(Intent.EXTRA_TEXT, link.toString())

    startActivity(Intent.createChooser(intent, "Share Link"))
}

この例では、デフォルトの共有 UI にリンクを共有するアプリのリストが自動的に表示されるので、数行のコードを追加するだけで自分のアプリへ実装できます。

アプリ内でユーザーに連絡先を選択させてメッセージを作成させるアクションは、実際には共有ダイアログで選択されたアプリに委任されます。また、他のアプリへ共有が委任されるので、連絡先のアクセス許可をユーザーに求める必要がなくなり、ユーザーは選択したアプリ内で展開された連絡先リストから選択できます。ソーシャル共有をより容易にするために、ソーシャル メディアのプレビューのメタデータを Dynamic Links に追加して、主要なソーシャル チャネルのリンクと一緒に表示できます。

しかし、テキストなしでリンクのみを送信しただけでは、魅力的な紹介にならないこともあります。リンクに短いメッセージと、可能であればより表現力豊かなプレゼンテーションを付け加えることで、紹介を受け取ったユーザーは提案されたアプリの価値(価値提案)を理解しやすくなります。

iOS

特典付きの紹介のスクリーンショット 共有シート付きの特典付きの紹介のスクリーンショット

Android

特典付きの紹介のスクリーンショット 共有シート付きの特典付きの紹介のスクリーンショット

これは前述の例よりも複雑ですが、アプローチはほぼ同じです。この画面には、招待の価値提案が記載された大きなグラフィックと、主要なソーシャル チャネルに共有するためのボタンが含まれます。この UI フローには多少の冗長性を持たせます。たとえば、メールでの招待に件名を追加するなど、チャネル固有のメッセージをカスタマイズできるように、いくつかの共有チャンネルが個別に表示されるようにします。この招待メニューは、次のように作成されます。

  • メール、テキスト メッセージ、リンクのコピーの共有ボタンが表示され、メッセージを適切にカスタマイズできます。メールには件名と、改行、画像、空白を含む長い本文を含めることができます。テキスト メッセージには短い本文を含めることができますが、空白は最小限にし、画像は添付できません。リンクのコピーでは、リンクがコピーされるだけです。
  • リンクに付け加える短い招待メッセージなど、その他のすべての用途にシステム共有の UI を使用します。
  • URL スキームを介したディープリンクや、アプリの招待状を処理するための特別なロジックを持つ他のアプリへのユニバーサル リンクは、自分の組織と他のアプリ間の提携外では機能せず、小規模の組織にとっては最良の選択肢ではありません。しかし、ユニバーサル リンクやディープリンクの動作を公に文書化しているアプリもあるかもしれません。サンプルでは、これのダミー バージョンを実装します。

まず、招待のコンテンツの種類を定義します。これは、招待に含まれる情報だけをカプセル化したもので、機能は含まれていません。このように、データ型から始めて、それがどのようにデータをまとめているかという観点から自分のコードについて考えてみます。

Swift

/// The content within an invite, with optional fields to accommodate all presenters.
/// This type could be modified to also include an image, for sending invites over email.
struct InviteContent {

  /// The subject of the message. Not used for invites without subjects, like text message invites.
  var subject: String?

  /// The body of the message. Indispensable content should go here.
  var body: String?

  /// The URL containing the invite. In link-copy cases, only this field will be used.
  var link: URL

}

Objective-C

/// The content within an invite, with optional fields to accommodate all presenters.
/// This type could be modified to also include an image, for sending invites over email.
@interface InviteContent : NSObject <NSCopying>

/// The subject of the message. Not used for invites without subjects, like text message invites.
@property (nonatomic, readonly, nullable) NSString *subject;

/// The body of the message. Indispensable content should go here.
@property (nonatomic, readonly, nullable) NSString *body;

/// The URL containing the invite. In link-copy cases, only this field will be used.
@property (nonatomic, readonly) NSURL *link;

- (instancetype)initWithSubject:(nullable NSString *)subject
                           body:(nullable NSString *)body
                           link:(NSURL *)link NS_DESIGNATED_INITIALIZER;

- (instancetype)init NS_UNAVAILABLE;

@end

Java

/**
 * The content of an invitation, with optional fields to accommodate all presenters.
 * This type could be modified to also include an image, for sending invites over email.
 */
public class InviteContent {

    /**
     * The subject of the message. Not used for invites without subjects, like SMS.
     **/
    @Nullable
    public final String subject;

    /**
     * The body of the message. Indispensable content should go here.
     **/
    @Nullable
    public final String body;

    /**
     * The URL containing the link to invite. In link-copy cases, only this field will be used.
     **/
    @NonNull
    public final Uri link;

    public InviteContent(@Nullable String subject, @Nullable String body, @NonNull Uri link) {
        // ...
    }

}

Kotlin

/**
 * The content of an invitation, with optional fields to accommodate all presenters.
 * This type could be modified to also include an image, for sending invites over email.
 */
data class InviteContent(
    /** The subject of the message. Not used for invites without subjects, like SMS.  */
    val subject: String?,
    /** The body of the message. Indispensable content should go here.  */
    val body: String?,
    /** The URL containing the link to invite. In link-copy cases, only this field will be used.  */
    val link: Uri
)

ここで必要なデータは URL のみです。これがなければ、ユーザーをアプリに招待できません。他のデータの要素は、メールを送信するという目的で明確に構成されているため、その他のケースでは少し不自然になることがあります。たとえば、テキスト メッセージ経由で招待を送信すると、リンクに加えられたキャッチフレーズがメールの件名のようになり、ソーシャル メディアに共有した場合は、リンクに加えられたテキストがメールの本文のようになる可能性があります。アプリで最適なバランスを見つけるには、これを自分で試してみる必要があります。確信を持てない場合は、Remote Config のようなサービスを使用してアプリの起動後にテキスト値を変更できます。

Swift

/// A type responsible for presenting an invite given using a specific method
/// given the content of the invite.
protocol InvitePresenter {

  /// The name of the presenter. User-visible.
  var name: String { get }

  /// An icon representing the invite method. User-visible.
  var icon: UIImage? { get }

  /// Whether or not the presenter's method is available. iOS devices that aren't phones
  /// may not be able to send texts, for example.
  var isAvailable: Bool { get }

  /// The content of the invite. Some of the content type's fields may be unused.
  var content: InviteContent { get }

  /// Designated initializer.
  init(content: InviteContent, presentingController: UIViewController)

  /// This method should cause the presenter to present the invite and then handle any actions
  /// required to complete the invite flow.
  func sendInvite()

}

Objective-C

/// A type responsible for presenting an invite given using a specific method
/// given the content of the invite.
@protocol InvitePresenter <NSObject>

/// The name of the presenter. User-visible.
@property (nonatomic, readonly) NSString *name;

/// An icon representing the invite method. User-visible.
@property (nonatomic, readonly, nullable) UIImage *icon;

/// Whether or not the presenter's method is available. iOS devices that aren't phones
/// may not be able to send texts, for example.
@property (nonatomic, readonly) BOOL isAvailable;

/// The content of the invite. Some of the content type's fields may be unused.
@property (nonatomic, readonly) InviteContent *content;

/// Designated initializer.
- (instancetype)initWithContent:(InviteContent *)content presentingViewController:(UIViewController *)controller;

/// This method should cause the presenter to present the invite and then handle any actions
/// required to complete the invite flow.
- (void)sendInvite;

@end

Java

/**
 * Presents the invite using a specific method, such as email or social.
 */
public class InvitePresenter {

    /**
     * The user-visible name of the invite method, like 'Email' or 'SMS'
     **/
    public final String name;

    /**
     * An icon representing the invite method.
     **/
    @DrawableRes
    public final int icon;

    /**
     * Whether or not the method is available on this device. For example, SMS is phone only.
     **/
    public final boolean isAvailable;

    /**
     * The Content of the invitation
     **/
    public final InviteContent content;

    public InvitePresenter(String name, @DrawableRes int icon, boolean isAvailable, InviteContent content) {
        // ...
    }

    /**
     * Send the invitation using the specified method.
     */
    public void sendInvite(Context context) {
        // ...
    }

}

Kotlin

/**
 * Presents the invite using a specific method, such as email or social.
 */
open class InvitePresenter(
    /** The user-visible name of the invite method, like 'Email' or 'SMS'  */
    val name: String,
    /** An icon representing the invite method.  */
    @param:DrawableRes @field:DrawableRes
    val icon: Int,
    /** Whether or not the method is available on this device. For example, SMS is phone only.  */
    val isAvailable: Boolean,
    /** The Content of the invitation  */
    val content: InviteContent
) {
    /**
     * Send the invitation using the specified method.
     */
    open fun sendInvite(context: Context) {
        // ...
    }
}

後は、選択した UI コンポーネントにこれを実装するだけです。この招待フローの完全な実装については、GitHub にあるサンプル(iOSAndroid)をご覧ください。

以上がユーザーが友だちに招待を送信できるようにするためのすべての方法であり、最も軽量な招待のソリューションです。多くの人気アプリでは、独自のバックエンドを通してメールを送信し、招待を行います。これにはメール送信サービスの統合が必要ですが、多くのメリットがあり、欠点はわずかです。

長所:

  • 送信前にユーザーが変更できない複雑なマークアップを持つメールを実現できます。
  • よりきめ細かな追跡や分析が可能になります(バックエンドで招待の成功と失敗を送信するなど)。

短所:

  • メールがスパムとしてフラグが立てられる可能性が高くなります。
  • メール配信サービスとの統合が必要です。
  • アプリ内で連絡先へのアクセス権限が必要です。

一般的に、独自のメール配信サービスを通して招待を送信することで、汎用性が犠牲になる一方、より一貫性があり、潜在的により表現豊かな招待の体験が実現できます。

アプリ内でリンク先コンテンツを開く

最後に、リンクの受信者にリンク先コンテンツを表示するには、アプリに渡されたリンクを受け取る必要があります。Dynamic Links SDK を使用すれば、簡単にリンクを受信できます。

iOS

iOS の場合、Dynamic Links を受信するには application:continueUserActivity:restorationHandler: メソッドを実装します。復元ハンドラ内で handleUniversalLink:completion: を呼び出すことで、Dynamic Links を取得できます。Dynamic Links がアプリに渡されている場合は、FIRDynamicLinkurl プロパティから取得できます。次に例を示します。

Objective-C

[[FIRDynamicLinks dynamicLinks]
    handleUniversalLink:userActivity.webpageURL
             completion:^(FIRDynamicLink * _Nullable dynamicLink,
                          NSError * _Nullable error) {
      NSString *link = dynamicLink.url;
      BOOL strongMatch = dynamicLink.matchConfidence == FIRDynamicLinkMatchConfidenceStrong;
      // ...
    }];

Swift

FIRDynamicLinks.dynamicLinks()?.handleUniversalLink(userActivity.webpageURL!) { (dynamiclink, error) in
    let link = dynamicLink.url
    let strongMatch = dynamicLink.matchConfidence == FIRDynamicLinkMatchConfidenceStrong
    // ...
}

また、application:openURL:options: メソッドで dynamicLinkFromCustomSchemeURL: を呼び出して、アプリに渡された Dynamic Links をカスタム スキーム URL として受信する必要があります。次に例を示します。

Objective-C

FIRDynamicLink *dynamicLink = [[FIRDynamicLinks dynamicLinks] dynamicLinkFromCustomSchemeURL:url];
if (dynamicLink) {
  NSString *link = dynamicLink.url;
  BOOL strongMatch = dynamicLink.matchConfidence == FIRDynamicLinkMatchConfidenceStrong;
  // ...
  return YES;
}

Swift

let dynamicLink = FIRDynamicLinks.dynamicLinks()?.dynamicLinkFromCustomSchemeURL(url)
if let dynamicLink = dynamicLink {
  let link = dynamicLink.url
  let strongMatch = dynamicLink.matchConfidence == FIRDynamicLinkMatchConfidenceStrong
  // ...
  return true
}

link パラメータの値を取得したことにより、リンク先コンテンツを受信者に表示したり、パラメータで指定されたデータを他のなんらかの方法で処理したりできるようになります。このタスクには、JLRoutes などの URL ルーティング ライブラリを利用できます。

特定の受信者を対象としたリンクを受信する場合、ユーザー固有のロジックを実行する前に、Dynamic Links の一致信頼度が strong であることを確認してください。

Android

Android では、getDynamicLink() メソッドを使用して Dynamic Links からデータを取得します。

Java

FirebaseDynamicLinks.getInstance()
        .getDynamicLink(getIntent())
        .addOnCompleteListener(new OnCompleteListener<PendingDynamicLinkData>() {
            @Override
            public void onComplete(@NonNull Task<PendingDynamicLinkData> task) {
                if (!task.isSuccessful()) {
                    // Handle error
                    // ...
                }

                FirebaseAppInvite invite = FirebaseAppInvite.getInvitation(task.getResult());
                if (invite != null) {
                    // Handle invite
                    // ...
                }
            }
        });

Kotlin

FirebaseDynamicLinks.getInstance()
        .getDynamicLink(intent)
        .addOnCompleteListener { task ->
            if (!task.isSuccessful) {
                // Handle error
                // ...
            }

            val invite = FirebaseAppInvite.getInvitation(task.result)
            if (invite != null) {
                // Handle invite
                // ...
            }
        }

link パラメータの値を取得したことにより、リンク先コンテンツを受信者に表示したり、パラメータで指定されたデータを他のなんらかの方法で処理したりできるようになります。このタスクには、URL ルーティング ライブラリを利用できます。