Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(appdistribution): add in-app-feedback support #1463

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 5 additions & 1 deletion appdistribution/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@

## Introduction

The Firebase App Distribution SDK enables you to display in-app alerts to your testers when new builds of your app are available to install. This quickstart aims to showcase how to use the App Distribution SDK to create and customize new build alerts for your testers. You can read more
The Firebase App Distribution SDK enables you to:
- display in-app alerts to your testers when new builds of your app are available to install;
- collect in-app feedback from your testers.

This quickstart aims to showcase how to use the App Distribution SDK. You can read more
about Firebase App Distribution [here](https://firebase.google.com/docs/app-distribution)!

## Getting Started
Expand Down
6 changes: 4 additions & 2 deletions appdistribution/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,10 @@ dependencies {
// Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom)
implementation platform('com.google.firebase:firebase-bom:31.2.0')

// ADD the SDK to the "prerelease" variant only (example)
implementation 'com.google.firebase:firebase-appdistribution-ktx:16.0.0-beta01'
implementation 'com.google.firebase:firebase-appdistribution-api-ktx:16.0.0-beta06'

// ADD the full SDK implementation to the "debug" variant only
debugImplementation 'com.google.firebase:firebase-appdistribution:16.0.0-beta06'

// For an optimal experience using App Distribution, add the Firebase SDK
// for Google Analytics. This is recommended, but not required.
Expand Down
3 changes: 3 additions & 0 deletions appdistribution/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<!-- Used by the Firebase App Distribution SDK to display in app feedback notifications -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>

<application
android:name="androidx.multidex.MultiDexApplication"
android:allowBackup="true"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,42 @@
package com.google.firebase.appdistributionquickstart.java;

import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;

import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;

import com.google.firebase.appdistribution.FirebaseAppDistribution;
import com.google.firebase.appdistribution.FirebaseAppDistributionException;
import com.google.firebase.appdistribution.InterruptionLevel;
import com.google.firebase.appdistributionquickstart.R;
import com.google.firebase.appdistributionquickstart.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity {

private static final String TAG = "AppDistribution-Quickstart";
private static final String TAG = "MainActivity.java";

// Declare the launcher at the top of your Activity/Fragment:
private final ActivityResultLauncher<String> requestPermissionLauncher =
registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> {
if (isGranted) {
showFeedbackNotification();
} else {
Toast.makeText(
MainActivity.this,
"You won't be able to tap a notification to send feedback because" +
" the notification permission was denied",
Toast.LENGTH_LONG
).show();
}
});

private FirebaseAppDistribution mFirebaseAppDistribution;

Expand All @@ -21,6 +47,14 @@ protected void onCreate(Bundle savedInstanceState) {
setContentView(binding.getRoot());

mFirebaseAppDistribution = FirebaseAppDistribution.getInstance();

binding.btShowNotification.setOnClickListener(view -> {
askNotificationPermission();
});

binding.btSendFeedback.setOnClickListener(view -> {
mFirebaseAppDistribution.startFeedback(R.string.feedback_additional_form_text);
});
}

@Override
Expand All @@ -37,4 +71,48 @@ public void onResume() {
}
});
}

@Override
protected void onDestroy() {
super.onDestroy();
// Hide the notification once this activity is destroyed
mFirebaseAppDistribution.cancelFeedbackNotification();
}
Comment on lines +75 to +80

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You shouldn't need this anymore now that we made the change to automatically hide the notification

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When does the automatic hide take place? I tried closing the Activity, but it didn't dismiss the notification. Is it when the app is closed?


private void showFeedbackNotification() {
mFirebaseAppDistribution.showFeedbackNotification(
R.string.feedback_additional_form_text,
InterruptionLevel.HIGH
);
}

private void askNotificationPermission() {
// This is only necessary for API level >= 33 (TIRAMISU)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think you want this check here anymore. Even if they are on an older device we still want to show the notification. On older devices the checkSelfPermission() call will return true.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc: @marinacoelho
The check is due to the POST_NOTIFICATIONS field which was introduced in API 33.
I have added an else branch to display the feedback notification on older devices.

Screenshot 2023-03-07 at 13 12 01

if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) ==
PackageManager.PERMISSION_GRANTED) {
// All set. We can post notifications
showFeedbackNotification();
} else if (shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS)) {
Log.i(TAG, "Showing customer rationale for requesting permission.");
new AlertDialog.Builder(this)
.setMessage("Using a notification to initiate feedback to the developer. " +
"To enable this feature, allow the app to post notifications."
)
.setPositiveButton("OK", (dialogInterface, i) -> {
Log.i(TAG, "Launching request for permission.");
requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS);
})
.setNegativeButton("No thanks", (dialogInterface, i) -> {
Log.i(TAG, "User denied permission request.");
})
.show();
} else {
// Directly ask for the permission
requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS);
}
} else {
showFeedbackNotification();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,23 +1,57 @@
package com.google.firebase.appdistributionquickstart.kotlin

import android.Manifest
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import com.google.firebase.appdistribution.FirebaseAppDistribution
import com.google.firebase.appdistribution.FirebaseAppDistributionException
import com.google.firebase.appdistribution.InterruptionLevel
import com.google.firebase.appdistribution.ktx.appDistribution
import com.google.firebase.appdistributionquickstart.R
import com.google.firebase.appdistributionquickstart.databinding.ActivityMainBinding
import com.google.firebase.ktx.Firebase

class KotlinMainActivity : AppCompatActivity() {

private lateinit var firebaseAppDistribution: FirebaseAppDistribution

// Declare the launcher at the top of your Activity/Fragment:
private val requestPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted: Boolean ->
if (isGranted) {
showFeedbackNotification()
} else {
Toast.makeText(
this@KotlinMainActivity,
"You won't be able to tap a notification to send feedback because" +
" the notification permission was denied",
Toast.LENGTH_LONG
).show()
}
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)

firebaseAppDistribution = Firebase.appDistribution

binding.btShowNotification.setOnClickListener {
askNotificationPermission()
}

binding.btSendFeedback.setOnClickListener {
firebaseAppDistribution.startFeedback(R.string.feedback_additional_form_text)
}
}

override fun onResume() {
Expand All @@ -34,10 +68,51 @@ class KotlinMainActivity : AppCompatActivity() {
}
}

override fun onDestroy() {
super.onDestroy()
// Hide the notification once this activity is destroyed
firebaseAppDistribution.cancelFeedbackNotification()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to Lee's comment above, I think we don't need this line here as well.

}

private fun showFeedbackNotification() {
firebaseAppDistribution.showFeedbackNotification(
R.string.feedback_additional_form_text,
InterruptionLevel.HIGH
)
}

private fun askNotificationPermission() {
// This is only necessary for API level >= 33 (TIRAMISU)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as Lee's comment above. And if we absolutely need to keep this check here, what do you think of changing this function a bit to use when instead of if / else if / else ? (Or switch / case on the Java file).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having a when here might work for Kotlin, since the language allows usage of when without a subject. But not sure if Java has a switch-case without a subject 🤔

if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) ==
PackageManager.PERMISSION_GRANTED
) {
// All set. We can post notifications
showFeedbackNotification()
} else if (shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS)) {
Log.i(TAG, "Showing customer rationale for requesting permission.")
AlertDialog.Builder(this)
.setMessage(
"Using a notification to initiate feedback to the developer. " +
"To enable this feature, allow the app to post notifications."
)
.setPositiveButton("OK") { _, _ ->
Log.i(TAG, "Launching request for permission.")
requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
}
.setNegativeButton("No thanks") { _, _ -> Log.i(TAG, "User denied permission request.") }
.show()
} else {
// Directly ask for the permission
requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
}
} else {
showFeedbackNotification()
}
}

companion object {

private const val TAG = "AppDistribution-Quickstart"
private const val TAG = "KotlinMainActivity.kt"
}
}
24 changes: 23 additions & 1 deletion appdistribution/app/src/main/res/layout/activity_main.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp"
tools:context=".MainActivity">
tools:context=".kotlin.KotlinMainActivity">

<ImageView
android:id="@+id/imageView"
Expand Down Expand Up @@ -34,4 +34,26 @@
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/imageView" />

<Button
android:id="@+id/btShowNotification"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="16dp"
android:text="@string/show_feedback_notification"
app:layout_constraintEnd_toEndOf="@+id/textView"
app:layout_constraintStart_toStartOf="@+id/textView"
app:layout_constraintTop_toBottomOf="@+id/textView" />

<Button
android:id="@+id/btSendFeedback"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/send_feedback"
app:layout_constraintEnd_toEndOf="@+id/btShowNotification"
app:layout_constraintStart_toStartOf="@+id/btShowNotification"
app:layout_constraintTop_toBottomOf="@+id/btShowNotification" />

</androidx.constraintlayout.widget.ConstraintLayout>
5 changes: 4 additions & 1 deletion appdistribution/app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
<resources>
<string name="app_name">App Distribution Quickstart</string>
<string name="textview_text">Welcome to the Firebase App Distribution Quickstart app. Press the button to trigger an analytics event!</string>
<string name="textview_text">Welcome to the Firebase App Distribution Quickstart app</string>

<string name="installation_id_fmt">Firebase Installation ID: %s</string>
<string name="show_feedback_notification">Show Feedback Notification</string>
<string name="send_feedback">Send Feedback</string>
<string name="feedback_additional_form_text">This is a placeholder for text from the developer that is shown to the tester before they provide feedback. It can contain <a href="https://firebase.google.com/docs/app-distribution">links</a> to more information.</string>
</resources>
Binary file modified appdistribution/docs/result.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.