diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000000..d2ff044963 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,2 @@ +[*.{java,kt}] +max_line_length = 120 diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000..c0f8950887 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,11 @@ +blank_issues_enabled: false +contact_links: + - name: 🐞 Bug in the Firebase SDK + url: https://github.com/firebase/firebase-android-sdk/issues/new/choose + about: Do you think you have found a bug in the Firebase Android SDK? + - name: 🤖 MLKit On-Device Issues + url: https://github.com/googlesamples/mlkit + about: The MLKit on-device SDK has moved out of Firebase and the best place to get support is on their samples repository. + - name: 🔥 Firebase Support + url: https://firebase.google.com/support/ + about: If you have an urgent issue with your app, please contact support. diff --git a/.github/ISSUE_TEMPLATE/quickstart_issue.md b/.github/ISSUE_TEMPLATE/quickstart_issue.md new file mode 100644 index 0000000000..7834967741 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/quickstart_issue.md @@ -0,0 +1,50 @@ +--- +name: ⚠️ Issue with the quickstart code +about: + Are you having issues running the code in this repository? +--- + + + + + +### Step 1: Describe your environment + + * Android device: _____ + * Android OS version: _____ + * Google Play Services version: _____ + * Firebase/Play Services SDK version: _____ + +### Step 2: Describe the problem: + +#### Steps to reproduce: + + 1. _____ + 2. _____ + 3. _____ + +#### Observed Results: + + * What happened? This could be a description, `logcat` output, etc. + +#### Expected Results: + + * What did you expect to happen? + +#### Relevant Code: + + ``` + // TODO(you): code here to reproduce the problem + ``` diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml new file mode 100644 index 0000000000..b653e9a4df --- /dev/null +++ b/.github/workflows/android.yml @@ -0,0 +1,36 @@ +name: Android CI + +on: + pull_request: + push: + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + distribution: 'zulu' + java-version: 17 + - name: Setup Gradle + uses: gradle/gradle-build-action@v2 + - name: Check Snippets + run: python scripts/checksnippets.py + # TODO(thatfiredev): remove this once github.com/firebase/quickstart-android/issues/1672 is fixed + - name: Remove Firebase Data Connect from CI + run: python scripts/ci_remove_fdc.py + - name: Copy mock google_services.json + run: ./copy_mock_google_services_json.sh + - name: Build with Gradle (Pull Request) + run: ./build_pull_request.sh + if: github.event_name == 'pull_request' + - name: Build with Gradle (Push) + run: ./gradlew clean ktlint assemble + if: github.event_name != 'pull_request' \ No newline at end of file diff --git a/.gitignore b/.gitignore index 10cc0ea8bd..2503d43f95 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,8 @@ build/ *.aar *.zip google-services.json + +.project +.settings +.classpath +.vscode diff --git a/.google/packaging.yaml b/.google/packaging.yaml index 560a671692..0defba8037 100644 --- a/.google/packaging.yaml +++ b/.google/packaging.yaml @@ -9,6 +9,7 @@ categories: [Getting Started] languages: [Java] solutions: [Mobile, Monetization, Startup, Enterprise] github: firebase/quickstart-android +branch: master level: BEGINNER icon: .google/icon.png license: apache2 diff --git a/.opensource/project.json b/.opensource/project.json new file mode 100644 index 0000000000..10c353bbd4 --- /dev/null +++ b/.opensource/project.json @@ -0,0 +1,38 @@ + +{ + "name": "Firebase Quickstarts for Android", + "parent": "quickstarts", + "type": "sample", + + "platforms": [ + "Android" + ], + + "content": "README.md", + + "pages" : { + "admob/README.md": "Admob", + "analytics/README.md": "Analytics", + "appdistribution/README.md": "App Distribution", + "app-indexing/README.md": "App Indexing", + "auth/README.md": "Authentication", + "config/README.md": "Remote Config", + "crash/README.md": "Crashlytics", + "database/README.md": "Realtime Database", + "dynamiclinks/README.md": "Dynamic Links", + "firestore/README.md": "Firestore", + "functions/README.md": "Cloud Functions", + "inappmessaging/README.md": "In App Messaging", + "messaging/README.md": "Cloud Messaging", + "mlkit/README.md": "ML Kit", + "perf/README.md": "Performance Monitoring", + "storage/README.md": "Cloud Storage" + }, + + "related": [ + "firebase/quickstart-ios", + "firebase/quickstart-js" + ], + + "tags": [] + } diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 43a9fd1cc3..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,42 +0,0 @@ -language: android -jdk: - - oraclejdk8 -android: - components: - - tools - - build-tools-24.0.2 - - android-24 - - platform-tools - - extra-google-google_play_services - - extra-google-m2repository - - extra-android-m2repository - - extra-android-support - - addon-google_apis-google-24 - licenses: - - 'android-sdk-preview-license-52d11cd2' - - 'android-sdk-license-.+' - - 'google-gdk-license-.+' - -before_cache: - - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock - - rm -fr $HOME/.gradle/caches/*/plugin-resolution/ -cache: - directories: - - $HOME/.gradle/caches/ - - $HOME/.gradle/wrapper/ - -env: - - SAMPLE=admob - - SAMPLE=analytics - - SAMPLE=app-indexing - - SAMPLE=auth - - SAMPLE=config - - SAMPLE=crash - - SAMPLE=database - - SAMPLE=dynamiclinks - - SAMPLE=invites - - SAMPLE=messaging - - SAMPLE=storage - -script: - - ./build.sh diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 713d89e4f5..66358dae31 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -49,41 +49,11 @@ See [below](#submit) for some guidelines. ### Submitting an Issue Before you submit your issue search the archive, maybe your question was already answered. -If your issue appears to be a bug, and hasn't been reported, open a new issue. -Help us to maximize the effort we can spend fixing issues and adding new -features, by not reporting duplicate issues. Providing the following information will increase the -chances of your issue being dealt with quickly: - -* **Overview of the Issue** - if an error is being thrown a non-minified stack trace helps -* **Motivation for or Use Case** - explain why this is a bug for you -* **Browsers and Operating System** - is this a problem with all browsers or only IE9? -* **Reproduce the Error** - provide a live example (using JSBin) or a unambiguous set of steps. -* **Related Issues** - has a similar issue been reported before? -* **Suggest a Fix** - if you can't fix the bug yourself, perhaps you can point to what might be - causing the problem (line of code or commit) +If your issue appears to be a bug, and hasn't been reported, open a new issue. Please fill out +all information in the issue template to maximize the chance that we can help you. **If you get help, help others. Good karma rulez!** -Here's a template to get you started: - -``` -Browser: -Browser version: -Operating system: -Operating system version: - -What steps will reproduce the problem: -1. -2. -3. - -What is the expected result? - -What happens instead of that? - -Please provide any other information below, and attach a screenshot if possible. -``` - ### Submitting a Pull Request Before you submit your pull request consider the following guidelines: @@ -94,40 +64,39 @@ Before you submit your pull request consider the following guidelines: * Make your changes in a new git branch: ```shell - git checkout -b my-fix-branch master + $ git checkout -b my-fix-branch master ``` * Create your patch, **including appropriate test cases**. * Follow our [Coding Rules](#rules). -* Avoid checking in files that shouldn't be tracked (e.g `node_modules`, `gulp-cache`, `.tmp`, `.idea`). We recommend using a [global](#global-gitignore) gitignore for this. -* Make sure **not** to include a recompiled version of the files found in `/css` and `/js` as part of your PR. We will generate these automatically. +* Avoid checking in files that shouldn't be tracked (e.g `*.class`, `.idea`, `.tmp`). We recommend using a [global](#global-gitignore) gitignore for this. * Commit your changes using a descriptive commit message. ```shell - git commit -a + $ git commit -a ``` Note: the optional commit `-a` command line option will automatically "add" and "rm" edited files. * Build your changes locally to ensure all the tests pass: ```shell - gulp + $ ./gradlew build ``` * Push your branch to GitHub: ```shell - git push origin my-fix-branch + $ git push origin my-fix-branch ``` -* In GitHub, send a pull request to `firebase-quickstart-web:master`. +* In GitHub, send a pull request to `firebase-quickstart-android:master`. * If we suggest changes then: * Make the required updates. * Rebase your branch and force push to your GitHub repository (this will update your Pull Request): ```shell - git rebase master -i - git push origin my-fix-branch -f + $ git rebase master -i + $ git push origin my-fix-branch -f ``` That's it! Thank you for your contribution! @@ -140,30 +109,30 @@ from the main (upstream) repository: * Delete the remote branch on GitHub either through the GitHub Android UI or your local shell as follows: ```shell - git push origin --delete my-fix-branch + $ git push origin --delete my-fix-branch ``` * Check out the master branch: ```shell - git checkout master -f + $ git checkout master -f ``` * Delete the local branch: ```shell - git branch -D my-fix-branch + $ git branch -D my-fix-branch ``` * Update your master with the latest upstream version: ```shell - git pull --ff upstream master + $ git pull --ff upstream master ``` ## Coding Rules -We generally follow the [Google JavaScript style guide][js-style-guide]. +Try to follow the same code style you see in the repository. ## Signing the CLA @@ -174,8 +143,5 @@ changes to be accepted, the CLA must be signed. It's a quick process, we promise [github]: https://github.com/firebase/quickstart-android [google-cla]: https://cla.developers.google.com -[js-style-guide]: http://google.github.io/styleguide/javascriptguide.xml -[py-style-guide]: http://google.github.io/styleguide/pyguide.html -[jsbin]: http://jsbin.com/ [stackoverflow]: http://stackoverflow.com/questions/tagged/firebase [global-gitignore]: https://help.github.com/articles/ignoring-files/#create-a-global-gitignore diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md deleted file mode 100644 index 99e18bb5da..0000000000 --- a/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,37 +0,0 @@ - -### Step 1: Are you in the right place? - - * For issues or feature requests related to the code **in this repository** file a Github issue. - * If you are filing an issue about **FCM in the background** make sure to read [#4](https://github.com/firebase/quickstart-android/issues/4) and [#89](https://github.com/firebase/quickstart-android/issues/89) first! - * For general technical questions, post a question on [StackOverflow](http://stackoverflow.com/) tagged appropriately. - * For general Firebase discussion, use the [firebase-talk google group](https://groups.google.com/forum/#!forum/firebase-talk) - * For help troubleshooting your application that does not fall under one of the above categories, reach out to the [personalized Firebase support channel](https://firebase.google.com/support/contact/troubleshooting/) - -### Step 2: Describe your environment - - * Android device: _____ - * Android OS version: _____ - * Google Play Services version: _____ - * Firebase/Play Services SDK version: _____ - -### Step 3: Describe the problem: - -#### Steps to reproduce: - - 1. _____ - 2. _____ - 3. _____ - -#### Observed Results: - - * What happened? This could be a description, `logcat` output, etc. - -#### Expected Results: - - * What did you expect to happen? - -#### Relevant Code: - - ``` - // TODO(you): code here to reproduce the problem - ``` diff --git a/LICENSE b/LICENSE index 9faf108657..9d950e0acf 100644 --- a/LICENSE +++ b/LICENSE @@ -187,7 +187,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2015 Google Inc + Copyright 2017 Google Inc Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index c555fc2a3c..7b312a37ea 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,44 @@ # Firebase Quickstarts for Android -[![Build Status](https://travis-ci.org/firebase/quickstart-android.svg?branch=master)](https://travis-ci.org/firebase/quickstart-android) - A collection of quickstart samples demonstrating the Firebase APIs on Android. For more information, see https://firebase.google.com. -Samples available: +## Samples + +You can open each of the following samples as an Android Studio project, and run +them on a mobile device or a virtual device (AVD). When doing so you need to +add each sample app you wish to try to a Firebase project on the [Firebase +console](https://console.firebase.google.com). You can add multiple sample apps +to the same Firebase project. There's no need to create separate projects for +each app. -- [Admob](admob) -- [Analytics](analytics) -- [App-Indexing](app-indexing) -- [Auth](auth) -- [Configuration](config) -- [Crash](crash) -- [Database](database) -- [Dynamic Links](dynamiclinks) -- [Invites](invites) -- [Messaging](messaging) -- [Storage](storage) +To add a sample app to a Firebase project, use the `applicationId` value specified +in the `app/build.gradle` file of the app as the Android package name. Download +the generated `google-services.json` file, and copy it to the `app/` directory of +the sample you wish to run. + +- [Admob](admob/README.md) +- [Firebase AI Logic](firebase-ai/README.md) +- [Analytics](analytics/README.md) +- [App Distribution](appdistribution/README.md) +- [Auth](auth/README.md) +- [Remote Config](config/README.md) +- [Crashlytics](crash/README.md) +- [Realtime Database](database/README.md) +- [Data Connect](dataconnect/README.md) +- [Firestore](firestore/README.md) +- [Cloud Functions for Firebase](functions/README.md) +- [In-App Messaging](inappmessaging/README.md) +- [Cloud Messaging](messaging/README.md) +- [Performance Monitoring](perf/README.md) +- [Cloud Storage for Firebase](storage/README.md) ## How to make contributions? Please read and follow the steps in the [CONTRIBUTING.md](CONTRIBUTING.md) -## License -See [LICENSE](LICENSE) +[![Actions Status][gh-actions-badge]][gh-actions] +[![SAM Score][sam-score-badge]][sam-score] + +[gh-actions]: https://github.com/firebase/quickstart-android/actions +[gh-actions-badge]: https://github.com/firebase/quickstart-android/actions/workflows/android.yml/badge.svg?branch=master&event=push +[sam-score]: https://ossbot.computer/samscore.html +[sam-score-badge]: https://ossbot.computer/samscorebadge?org=firebase&repo=quickstart-android diff --git a/admob/README.md b/admob/README.md index bb4edca10a..1b2d71a80a 100644 --- a/admob/README.md +++ b/admob/README.md @@ -14,8 +14,11 @@ Getting Started --------------- - [Add Firebase to your Android Project](https://firebase.google.com/docs/android/setup). +- Configure your AdMob app id: + - In `src/main/res/values/strings.xml` change the `admob_app_id` string to your AdMob app id. + - Note that this ID is used in two places: `AndroidManifest.xml` and `MainActivity` - Run the sample on your Android device or emulator. -- The running sample displays a test banner ad and a test interstitial add. +- The running sample displays a test banner ad and a test interstitial ad. Result ----------- diff --git a/admob/app/build.gradle b/admob/app/build.gradle deleted file mode 100644 index 9ee5bf3d56..0000000000 --- a/admob/app/build.gradle +++ /dev/null @@ -1,46 +0,0 @@ -apply plugin: 'com.android.application' - -android { - compileSdkVersion 24 - buildToolsVersion "24.0.2" - - defaultConfig { - applicationId "com.google.samples.quickstart.admobexample" - minSdkVersion 9 - targetSdkVersion 24 - versionCode 1 - versionName "1.0" - - testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" - } - - buildTypes { - release { - minifyEnabled true - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - } - } - - packagingOptions { - exclude 'LICENSE.txt' - } -} - -configurations.all { - resolutionStrategy.force 'com.android.support:support-annotations:24.2.1' -} - -dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) - // AppCompat - compile 'com.android.support:appcompat-v7:24.2.1' - - // [START gradle_play_config] - compile 'com.google.firebase:firebase-ads:9.8.0' - // [END gradle_play_config] - - androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2' - androidTestCompile 'com.android.support.test:runner:0.5' -} - -apply plugin: 'com.google.gms.google-services' diff --git a/admob/app/build.gradle.kts b/admob/app/build.gradle.kts new file mode 100644 index 0000000000..2cea71b221 --- /dev/null +++ b/admob/app/build.gradle.kts @@ -0,0 +1,75 @@ +import com.android.build.gradle.internal.tasks.factory.dependsOn +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + +plugins { + id("com.android.application") + id("kotlin-android") + id("com.google.gms.google-services") +} + +tasks { + check.dependsOn("assembleDebugAndroidTest") +} + +android { + namespace = "com.google.samples.quickstart.admobexample" + compileSdk = 36 + + defaultConfig { + applicationId = "com.google.samples.quickstart.admobexample" + minSdk = 23 + targetSdk = 36 + versionCode = 1 + versionName = "1.0" + multiDexEnabled = true + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + getByName("release") { + isMinifyEnabled = false + proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro") + } + } + packaging { + resources.excludes += "LICENSE.txt" + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + kotlin { + compilerOptions { + jvmTarget = JvmTarget.JVM_17 + } + } + buildFeatures { + viewBinding = true + } +} + +dependencies { + implementation(project(":internal:lintchecks")) + implementation(project(":internal:chooserx")) + implementation("androidx.appcompat:appcompat:1.7.1") + implementation("com.google.android.material:material:1.12.0") + implementation("androidx.browser:browser:1.5.0") + implementation("androidx.navigation:navigation-fragment-ktx:2.9.3") + implementation("androidx.navigation:navigation-ui-ktx:2.9.3") + + implementation("com.google.android.gms:play-services-ads:23.3.0") + + // Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom) + implementation(platform("com.google.firebase:firebase-bom:34.0.0")) + + // For an optimal experience using AdMob, add the Firebase SDK + // for Google Analytics. This is recommended, but not required. + implementation("com.google.firebase:firebase-analytics") + + debugImplementation("androidx.fragment:fragment-testing:1.8.8") + androidTestImplementation("androidx.test.espresso:espresso-core:3.7.0") + androidTestImplementation("androidx.test:rules:1.7.0") + androidTestImplementation("androidx.test:runner:1.7.0") + androidTestImplementation("androidx.test.ext:junit:1.3.0") +} diff --git a/admob/app/proguard-rules.pro b/admob/app/proguard-rules.pro index 59d7489c33..58708ab1fa 100644 --- a/admob/app/proguard-rules.pro +++ b/admob/app/proguard-rules.pro @@ -2,7 +2,7 @@ # By default, the flags in this file are appended to flags specified # in ${sdk.dir}/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the proguardFiles -# directive in build.gradle. +# directive in build.gradle.kts. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html diff --git a/admob/app/src/androidTest/java/com/google/samples/quickstart/admobexample/AdViewIdlingResource.java b/admob/app/src/androidTest/java/com/google/samples/quickstart/admobexample/AdViewIdlingResource.java deleted file mode 100644 index 73722e0813..0000000000 --- a/admob/app/src/androidTest/java/com/google/samples/quickstart/admobexample/AdViewIdlingResource.java +++ /dev/null @@ -1,69 +0,0 @@ -package com.google.samples.quickstart.admobexample; - -import android.support.test.espresso.IdlingResource; - -import com.google.android.gms.ads.AdListener; -import com.google.android.gms.ads.AdView; - -/** - * Espresso idling resource for monitoring an AdView. - */ -public class AdViewIdlingResource implements IdlingResource { - - private AdView mAdView; - private AdListener mAdListener; - private ResourceCallback mResourceCallback; - private boolean mIsLoadingAd = false; - - public AdViewIdlingResource(AdView adView) { - if (adView == null) { - throw new IllegalArgumentException( - "Can't initialize AdViewIdlingResource with null AdView."); - } - - this.mAdView = adView; - this.mAdListener = new AdListener() { - @Override - public void onAdFailedToLoad(int i) { - transitionToIdle(); - } - - @Override - public void onAdLoaded() { - transitionToIdle(); - } - }; - - mAdView.setAdListener(mAdListener); - } - - @Override - public String getName() { - return "AdViewIdlingResource:" + mAdView; - } - - @Override - public boolean isIdleNow() { - boolean idle = !mIsLoadingAd; - if (idle) { - transitionToIdle(); - } - return idle; - } - - @Override - public void registerIdleTransitionCallback(ResourceCallback callback) { - this.mResourceCallback = callback; - } - - public void setIsLoadingAd(boolean isLoadingAd) { - this.mIsLoadingAd = isLoadingAd; - } - - private void transitionToIdle() { - mIsLoadingAd = false; - if (mResourceCallback != null) { - mResourceCallback.onTransitionToIdle(); - } - } -} diff --git a/admob/app/src/androidTest/java/com/google/samples/quickstart/admobexample/InterstitialAdTest.java b/admob/app/src/androidTest/java/com/google/samples/quickstart/admobexample/InterstitialAdTest.java deleted file mode 100644 index a2ead579c5..0000000000 --- a/admob/app/src/androidTest/java/com/google/samples/quickstart/admobexample/InterstitialAdTest.java +++ /dev/null @@ -1,70 +0,0 @@ -package com.google.samples.quickstart.admobexample; - - -import android.support.test.espresso.Espresso; -import android.support.test.espresso.ViewInteraction; -import android.support.test.rule.ActivityTestRule; -import android.support.test.runner.AndroidJUnit4; -import android.test.suitebuilder.annotation.LargeTest; - -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; - -import static android.support.test.espresso.Espresso.onView; -import static android.support.test.espresso.action.ViewActions.click; -import static android.support.test.espresso.assertion.ViewAssertions.matches; -import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; -import static android.support.test.espresso.matcher.ViewMatchers.withContentDescription; -import static android.support.test.espresso.matcher.ViewMatchers.withId; -import static android.support.test.espresso.matcher.ViewMatchers.withText; -import static org.hamcrest.Matchers.allOf; - -@LargeTest -@RunWith(AndroidJUnit4.class) -public class InterstitialAdTest { - - private AdViewIdlingResource mAdResource; - - @Rule - public ActivityTestRule mActivityTestRule = new ActivityTestRule<>(MainActivity.class); - - @Before - public void setUp() { - mAdResource = new AdViewIdlingResource(mActivityTestRule.getActivity().getAdView()); - Espresso.registerIdlingResources(mAdResource); - } - - @After - public void tearDown() { - Espresso.unregisterIdlingResources(mAdResource); - } - - @Test - public void interstitialAdTest() { - // Wait for ad to load - mAdResource.setIsLoadingAd(true); - - // Confirm that banner ad appears - onView(withId(R.id.adView)) - .check(matches(isDisplayed())); - - // Click show interstitial button - ViewInteraction showInterstitialButton = onView( - allOf(withId(R.id.load_interstitial_button), - withText(R.string.interstitial_button_text), - isDisplayed())); - showInterstitialButton.perform(click()); - - // Click close interstitial button - ViewInteraction closeInterstitialButton = onView( - allOf(withContentDescription("Interstitial close button"), isDisplayed())); - closeInterstitialButton.perform(click()); - - // Confirm that we're on the second activity - onView(withText(R.string.second_activity_content)) - .check(matches(isDisplayed())); - } -} diff --git a/admob/app/src/main/AndroidManifest.xml b/admob/app/src/main/AndroidManifest.xml index 1b9a0a2833..95424df15c 100644 --- a/admob/app/src/main/AndroidManifest.xml +++ b/admob/app/src/main/AndroidManifest.xml @@ -1,6 +1,6 @@ + xmlns:tools="http://schemas.android.com/tools"> + + + + + + android:name=".EntryChoiceActivity" + android:label="@string/app_name" + android:exported="true"> + + + @@ -31,14 +50,6 @@ android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize" android:theme="@android:style/Theme.Translucent" /> - - - diff --git a/admob/app/src/main/java/com/google/samples/quickstart/admobexample/EntryChoiceActivity.kt b/admob/app/src/main/java/com/google/samples/quickstart/admobexample/EntryChoiceActivity.kt new file mode 100644 index 0000000000..7281a17aaa --- /dev/null +++ b/admob/app/src/main/java/com/google/samples/quickstart/admobexample/EntryChoiceActivity.kt @@ -0,0 +1,23 @@ +package com.google.samples.quickstart.admobexample + +import android.content.Intent +import com.firebase.example.internal.BaseEntryChoiceActivity +import com.firebase.example.internal.Choice + +class EntryChoiceActivity : BaseEntryChoiceActivity() { + + override fun getChoices(): List { + return listOf( + Choice( + "Java", + "Run the Firebase Admob quickstart written in Java.", + Intent(this, com.google.samples.quickstart.admobexample.java.MainActivity::class.java), + ), + Choice( + "Kotlin", + "Run the Firebase Admob quickstart written in Kotlin.", + Intent(this, com.google.samples.quickstart.admobexample.kotlin.MainActivity::class.java), + ), + ) + } +} diff --git a/admob/app/src/main/java/com/google/samples/quickstart/admobexample/MainActivity.java b/admob/app/src/main/java/com/google/samples/quickstart/admobexample/MainActivity.java deleted file mode 100644 index 9d547211d5..0000000000 --- a/admob/app/src/main/java/com/google/samples/quickstart/admobexample/MainActivity.java +++ /dev/null @@ -1,164 +0,0 @@ -/** - * Copyright Google Inc. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.google.samples.quickstart.admobexample; - -import android.content.Intent; -import android.os.Bundle; -import android.support.annotation.VisibleForTesting; -import android.support.v7.app.AppCompatActivity; -import android.util.Log; -import android.view.View; -import android.widget.Button; -import com.google.android.gms.ads.AdListener; -// [SNIPPET load_banner_ad] -// Load an ad into the AdView. -// [START load_banner_ad] -import com.google.android.gms.ads.AdRequest; -import com.google.android.gms.ads.AdView; -// [START_EXCLUDE] -import com.google.android.gms.ads.InterstitialAd; -// [END_EXCLUDE] - -public class MainActivity extends AppCompatActivity { - - private static final String TAG = "MainActivity"; - - private AdView mAdView; - // [START_EXCLUDE] - private InterstitialAd mInterstitialAd; - private Button mLoadInterstitialButton; - // [END_EXCLUDE] - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - - mAdView = (AdView) findViewById(R.id.adView); - AdRequest adRequest = new AdRequest.Builder().build(); - mAdView.loadAd(adRequest); - // [END load_banner_ad] - - // AdMob ad unit IDs are not currently stored inside the google-services.json file. - // Developers using AdMob can store them as custom values in a string resource file or - // simply use constants. Note that the ad units used here are configured to return only test - // ads, and should not be used outside this sample. - - // [START instantiate_interstitial_ad] - // Create an InterstitialAd object. This same object can be re-used whenever you want to - // show an interstitial. - mInterstitialAd = new InterstitialAd(this); - mInterstitialAd.setAdUnitId(getString(R.string.interstitial_ad_unit_id)); - // [END instantiate_interstitial_ad] - - // [START create_interstitial_ad_listener] - mInterstitialAd.setAdListener(new AdListener() { - @Override - public void onAdClosed() { - requestNewInterstitial(); - beginSecondActivity(); - } - - @Override - public void onAdLoaded() { - // Ad received, ready to display - // [START_EXCLUDE] - if (mLoadInterstitialButton != null) { - mLoadInterstitialButton.setEnabled(true); - } - // [END_EXCLUDE] - } - - @Override - public void onAdFailedToLoad(int i) { - // See https://goo.gl/sCZj0H for possible error codes. - Log.w(TAG, "onAdFailedToLoad:" + i); - } - }); - // [END create_interstitial_ad_listener] - - // [START display_interstitial_ad] - mLoadInterstitialButton = (Button) findViewById(R.id.load_interstitial_button); - mLoadInterstitialButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (mInterstitialAd.isLoaded()) { - mInterstitialAd.show(); - } else { - beginSecondActivity(); - } - } - }); - // [END display_interstitial_ad] - - // Disable button if an interstitial ad is not loaded yet. - mLoadInterstitialButton.setEnabled(mInterstitialAd.isLoaded()); - } - - /** - * Load a new interstitial ad asynchronously. - */ - // [START request_new_interstitial] - private void requestNewInterstitial() { - AdRequest adRequest = new AdRequest.Builder() - .build(); - - mInterstitialAd.loadAd(adRequest); - } - // [END request_new_interstitial] - - private void beginSecondActivity() { - Intent intent = new Intent(this, SecondActivity.class); - startActivity(intent); - } - - // [START add_lifecycle_methods] - /** Called when leaving the activity */ - @Override - public void onPause() { - if (mAdView != null) { - mAdView.pause(); - } - super.onPause(); - } - - /** Called when returning to the activity */ - @Override - public void onResume() { - super.onResume(); - if (mAdView != null) { - mAdView.resume(); - } - if (!mInterstitialAd.isLoaded()) { - requestNewInterstitial(); - } - } - - /** Called before the activity is destroyed */ - @Override - public void onDestroy() { - if (mAdView != null) { - mAdView.destroy(); - } - super.onDestroy(); - } - // [END add_lifecycle_methods] - - @VisibleForTesting - AdView getAdView() { - return mAdView; - } -} diff --git a/admob/app/src/main/java/com/google/samples/quickstart/admobexample/java/FirstFragment.java b/admob/app/src/main/java/com/google/samples/quickstart/admobexample/java/FirstFragment.java new file mode 100644 index 0000000000..f48340d807 --- /dev/null +++ b/admob/app/src/main/java/com/google/samples/quickstart/admobexample/java/FirstFragment.java @@ -0,0 +1,156 @@ +package com.google.samples.quickstart.admobexample.java; + +import android.os.Bundle; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; +import androidx.fragment.app.Fragment; +import androidx.navigation.fragment.NavHostFragment; + +import com.google.android.gms.ads.AdRequest; +import com.google.android.gms.ads.AdView; +import com.google.android.gms.ads.FullScreenContentCallback; +import com.google.android.gms.ads.LoadAdError; +import com.google.android.gms.ads.MobileAds; +import com.google.android.gms.ads.interstitial.InterstitialAd; +import com.google.android.gms.ads.interstitial.InterstitialAdLoadCallback; +import com.google.samples.quickstart.admobexample.R; +import com.google.samples.quickstart.admobexample.databinding.FragmentFirstBinding; + +class FirstFragment extends Fragment { + + private static final String TAG = "MainActivity"; + private static final String TEST_APP_ID = "ca-app-pub-3940256099942544~3347511713"; + + private AdView mAdView; + private InterstitialAd mInterstitialAd; + private Button mLoadInterstitialButton; + private FragmentFirstBinding binding; + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + binding = FragmentFirstBinding.inflate(inflater, container, false); + return binding.getRoot(); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + checkIds(); + + // Initialize the Google Mobile Ads SDK + MobileAds.initialize(getContext()); + + mAdView = binding.adView; + + requestNewInterstitial(); + + mLoadInterstitialButton = binding.loadInterstitialButton; + mLoadInterstitialButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (mInterstitialAd != null) { + mInterstitialAd.show(getActivity()); + } else { + beginSecondActivity(); + } + } + }); + + // Disable button if an interstitial ad is not loaded yet. + mLoadInterstitialButton.setEnabled(mInterstitialAd != null); + } + + /** + * Load a new interstitial ad asynchronously. + */ + private void requestNewInterstitial() { + AdRequest adRequest = new AdRequest.Builder().build(); + mAdView.loadAd(adRequest); + + // AdMob ad unit IDs are not currently stored inside the google-services.json file. + // Developers using AdMob can store them as custom values in a string resource file or + // simply use constants. Note that the ad units used here are configured to return only test + // ads, and should not be used outside this sample. + InterstitialAd.load(getContext(), getString(R.string.interstitial_ad_unit_id), adRequest, new InterstitialAdLoadCallback() { + @Override + public void onAdLoaded(@NonNull InterstitialAd interstitialAd) { + super.onAdLoaded(interstitialAd); + mInterstitialAd = interstitialAd; + + // Ad received, ready to display + if (mLoadInterstitialButton != null) { + mLoadInterstitialButton.setEnabled(true); + } + + mInterstitialAd.setFullScreenContentCallback(new FullScreenContentCallback() { + @Override + public void onAdDismissedFullScreenContent() { + super.onAdDismissedFullScreenContent(); + requestNewInterstitial(); + beginSecondActivity(); + } + }); + } + + @Override + public void onAdFailedToLoad(@NonNull LoadAdError loadAdError) { + super.onAdFailedToLoad(loadAdError); + mInterstitialAd = null; + Log.w(TAG, "onAdFailedToLoad:" + loadAdError.getMessage()); + } + }); + } + + private void beginSecondActivity() { + NavHostFragment.findNavController(this).navigate(R.id.action_FirstFragment_to_SecondFragment); + } + + /** Called when leaving the activity */ + @Override + public void onPause() { + if (mAdView != null) { + mAdView.pause(); + } + super.onPause(); + } + + /** Called when returning to the activity */ + @Override + public void onResume() { + super.onResume(); + if (mAdView != null) { + mAdView.resume(); + } + if (mInterstitialAd == null) { + requestNewInterstitial(); + } + } + + /** Called before the activity is destroyed */ + @Override + public void onDestroy() { + if (mAdView != null) { + mAdView.destroy(); + } + super.onDestroy(); + } + + @VisibleForTesting + public AdView getAdView() { + return mAdView; + } + + private void checkIds() { + if (TEST_APP_ID.equals(getString(R.string.admob_app_id))) { + Log.w(TAG, "Your admob_app_id is not configured correctly, please see the README"); + } + } +} diff --git a/admob/app/src/main/java/com/google/samples/quickstart/admobexample/SecondActivity.java b/admob/app/src/main/java/com/google/samples/quickstart/admobexample/java/MainActivity.java similarity index 65% rename from admob/app/src/main/java/com/google/samples/quickstart/admobexample/SecondActivity.java rename to admob/app/src/main/java/com/google/samples/quickstart/admobexample/java/MainActivity.java index 7bc6fa3518..db9593f6be 100644 --- a/admob/app/src/main/java/com/google/samples/quickstart/admobexample/SecondActivity.java +++ b/admob/app/src/main/java/com/google/samples/quickstart/admobexample/java/MainActivity.java @@ -13,18 +13,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.google.samples.quickstart.admobexample; +package com.google.samples.quickstart.admobexample.java; import android.os.Bundle; -import android.support.v7.app.AppCompatActivity; +import androidx.appcompat.app.AppCompatActivity; +import androidx.navigation.Navigation; +import com.google.samples.quickstart.admobexample.R; -public class SecondActivity extends AppCompatActivity { +public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.second_activity); + setContentView(R.layout.activity_main); + Navigation.findNavController(this, R.id.nav_host_fragment).setGraph(R.navigation.nav_graph_java); } } diff --git a/admob/app/src/main/java/com/google/samples/quickstart/admobexample/java/SecondFragment.java b/admob/app/src/main/java/com/google/samples/quickstart/admobexample/java/SecondFragment.java new file mode 100644 index 0000000000..360b797e05 --- /dev/null +++ b/admob/app/src/main/java/com/google/samples/quickstart/admobexample/java/SecondFragment.java @@ -0,0 +1,17 @@ +package com.google.samples.quickstart.admobexample.java; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import androidx.fragment.app.Fragment; +import com.google.samples.quickstart.admobexample.R; + +class SecondFragment extends Fragment { + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_second, container, false); + } + +} diff --git a/admob/app/src/main/java/com/google/samples/quickstart/admobexample/kotlin/FirstFragment.kt b/admob/app/src/main/java/com/google/samples/quickstart/admobexample/kotlin/FirstFragment.kt new file mode 100644 index 0000000000..1aae046328 --- /dev/null +++ b/admob/app/src/main/java/com/google/samples/quickstart/admobexample/kotlin/FirstFragment.kt @@ -0,0 +1,143 @@ +package com.google.samples.quickstart.admobexample.kotlin + +import android.os.Bundle +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Button +import androidx.fragment.app.Fragment +import androidx.navigation.fragment.findNavController +import com.google.android.gms.ads.AdRequest +import com.google.android.gms.ads.AdView +import com.google.android.gms.ads.FullScreenContentCallback +import com.google.android.gms.ads.LoadAdError +import com.google.android.gms.ads.MobileAds +import com.google.android.gms.ads.interstitial.InterstitialAd +import com.google.android.gms.ads.interstitial.InterstitialAdLoadCallback +import com.google.samples.quickstart.admobexample.R +import com.google.samples.quickstart.admobexample.databinding.FragmentFirstBinding + +class FirstFragment : Fragment() { + + private var _binding: FragmentFirstBinding? = null + private val binding get() = _binding!! + private var interstitialAd: InterstitialAd? = null + private lateinit var adView: AdView + private lateinit var loadInterstitialButton: Button + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View { + _binding = FragmentFirstBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + adView = binding.adView + loadInterstitialButton = binding.loadInterstitialButton + + checkIds() + + // Initialize the Google Mobile Ads SDK + MobileAds.initialize(requireContext()) + + requestNewInterstitial() + + loadInterstitialButton.setOnClickListener { + if (interstitialAd != null) { + interstitialAd?.show(requireActivity()) + } else { + goToNextFragment() + } + } + + // Disable button if an interstitial ad is not loaded yet. + loadInterstitialButton.isEnabled = interstitialAd != null + } + + /** + * Load a new interstitial ad asynchronously. + */ + private fun requestNewInterstitial() { + // AdMob ad unit IDs are not currently stored inside the google-services.json file. + // Developers using AdMob can store them as custom values in a string resource file or + // simply use constants. Note that the ad units used here are configured to return only test + // ads, and should not be used outside this sample. + val adRequest = AdRequest.Builder().build() + + adView.loadAd(adRequest) + + InterstitialAd.load( + requireContext(), + getString(R.string.interstitial_ad_unit_id), + adRequest, + object : InterstitialAdLoadCallback() { + override fun onAdLoaded(ad: InterstitialAd) { + super.onAdLoaded(ad) + interstitialAd = ad + // Ad received, ready to display + loadInterstitialButton.isEnabled = true + + interstitialAd?.fullScreenContentCallback = object : FullScreenContentCallback() { + override fun onAdDismissedFullScreenContent() { + super.onAdDismissedFullScreenContent() + goToNextFragment() + } + } + } + + override fun onAdFailedToLoad(error: LoadAdError) { + super.onAdFailedToLoad(error) + interstitialAd = null + Log.w(TAG, "onAdFailedToLoad:${error.message}") + } + }, + ) + } + + private fun goToNextFragment() { + findNavController().navigate(R.id.action_FirstFragment_to_SecondFragment) + } + + /** Called when leaving the activity */ + override fun onPause() { + adView.pause() + super.onPause() + } + + /** Called when returning to the activity */ + override fun onResume() { + super.onResume() + adView.resume() + if (interstitialAd == null) { + requestNewInterstitial() + } + } + + /** Called before the activity is destroyed */ + override fun onDestroy() { + adView.destroy() + super.onDestroy() + } + + private fun checkIds() { + if (TEST_APP_ID == getString(R.string.admob_app_id)) { + Log.w(TAG, "Your admob_app_id is not configured correctly, please see the README") + } + } + + companion object { + private const val TAG = "FirstFragment" + private const val TEST_APP_ID = "ca-app-pub-3940256099942544~3347511713" + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } +} diff --git a/admob/app/src/main/java/com/google/samples/quickstart/admobexample/kotlin/MainActivity.kt b/admob/app/src/main/java/com/google/samples/quickstart/admobexample/kotlin/MainActivity.kt new file mode 100644 index 0000000000..6cc6424b35 --- /dev/null +++ b/admob/app/src/main/java/com/google/samples/quickstart/admobexample/kotlin/MainActivity.kt @@ -0,0 +1,16 @@ +package com.google.samples.quickstart.admobexample.kotlin + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import androidx.navigation.findNavController +import com.google.samples.quickstart.admobexample.R + +class MainActivity : AppCompatActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + + findNavController(R.id.nav_host_fragment).setGraph(R.navigation.nav_graph_kotlin) + } +} diff --git a/admob/app/src/main/java/com/google/samples/quickstart/admobexample/kotlin/SecondFragment.kt b/admob/app/src/main/java/com/google/samples/quickstart/admobexample/kotlin/SecondFragment.kt new file mode 100644 index 0000000000..603d842541 --- /dev/null +++ b/admob/app/src/main/java/com/google/samples/quickstart/admobexample/kotlin/SecondFragment.kt @@ -0,0 +1,15 @@ +package com.google.samples.quickstart.admobexample.kotlin + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import com.google.samples.quickstart.admobexample.R + +class SecondFragment : Fragment() { + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + return inflater.inflate(R.layout.fragment_second, container, false) + } +} diff --git a/admob/app/src/main/res/layout/activity_main.xml b/admob/app/src/main/res/layout/activity_main.xml index fc44409995..7b3dcd099a 100644 --- a/admob/app/src/main/res/layout/activity_main.xml +++ b/admob/app/src/main/res/layout/activity_main.xml @@ -1,37 +1,20 @@ - - - - - -