Android App: Build It Bigger!

I love Udacity, and everyone knows that, and I also have been pursuing an Android Developer Nanodegree there since around September. I always wanted to blag about the experience, but I'm just too lazy. In fact, I always had to squeeze time finishing the assignments. They usually give you about two months to work on each project, which is far more than enough, because from my own experience, I usually only spend two and a half weeks to finish the supporting courses and build the project. Therefore, finishing the whole Degree in four months is actually possible. But as a full-time undergraduate trying to balance different workloads, I usually waited until holidays or semester break to finish the courses and assignment. I finished Project 3 around two months ago (Chinese New Year Holiday), which means I had three months to work on Project 4. Since the deadline is in early May, which is exam season, I realized I had to start early. I spent a whole week finishing the supporting course and the past couple days working on Project 4, and in the meantime, I also jotted down my experience here.

A little background, Project 4 is about finishing a joke telling app that consists of four modules:
  1. A Java library that provides jokes
  2. A Google Cloud Endpoints (GCE) project that serves those jokes
  3. An Android Library containing an activity for displaying jokes
  4. An Android app that fetches jokes from the GCE module and passes them to the Android Library for display
And using Gradle:
    1. Add free and paid flavors to an app, and set up your build to share code between them
    2. Factor reusable functionality into a Java library
    3. Factor reusable Android functionality into an Android library
    4. Configure a multi project build to compile your libraries and app
    5. Use the Gradle App Engine plugin to deploy a backend
    6. Configure an integration test suite that runs against the local App Engine development server



    This is freakin' awesome, I like jokes and puns so much and this time I get to supply my own jokes!!! (Take that, Indy, the only person who doesn't like my puns!) Now you know how psyched I am, let's get started!

    Comb sweet comb!

    Step 1: Create a Java Library

    1. After getting the starter code https://github.com/kevguy/JokeTellingApp/tree/Stage00_Starter_Code, and loading up your Android Studio, right click on app → add a new module → add a new Java library called "javaJoker" → inside is a class called "Joker"
    2. Check out settings.gradle, it's changed from

      include ':app'

      to  
      include ':app', 'javajoker'

    3. Then add dependencies in build.gradle (app):

      apply plugin: 'com.android.application'
      
      android {
          compileSdkVersion 22
          buildToolsVersion "22.0.1"
      
          defaultConfig {
              applicationId "com.udacity.gradle.builditbigger"
              minSdkVersion 10
              targetSdkVersion 22
              versionCode 1
              versionName "1.0"
          }
          buildTypes {
              release {
                  minifyEnabled false
                  proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
              }
          }
      }
      
      dependencies {
          compile fileTree(dir: 'libs', include: ['*.jar'])
          compile 'com.android.support:appcompat-v7:22.1.1'
          // Added for AdMob
          compile 'com.google.android.gms:play-services:7.3.0'
          compile project(":javajoker")
      }

    4. Replace everything in Joker.java with the following content:
      package com.example;
      
      public class Joker {
      
          public String tellJoke() {
              return "Microsoft gives you Windows, Linux gives you a home!";
          }
      
      }

    5. Go to MainActivity.java, namely the function tellJoke(), insert the following content:
       public void tellJoke(View view){
              Joker joker = new Joker();
      
              Toast.makeText(this, joker.tellJoke(), Toast.LENGTH_SHORT).show();
          }

    6. You can try to run the app and check it out.
    7. You can check out the code at https://github.com/kevguy/JokeTellingApp/tree/Stage01_Java_Library

    Step 2: Create an Android Library

    1. Right click on app and add a new Android Library (I called the libary "JokeFactory")


    2. Add dependencies in build.gradle (app)

      compile project(":jokefactory")
      




    3. Inside jokefactory.java, right click com.eample.android.jokefactory and add a new blank activity (I called it the DisplayJokeActivity)
    4. Go to the activity_display_joke.xml of this activity and add a TextView:

      <?xml version="1.0" encoding="utf-8"?>
      <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
          xmlns:tools="http://schemas.android.com/tools"
          android:layout_width="match_parent"
          android:layout_height="match_parent"
          android:paddingBottom="@dimen/activity_vertical_margin"
          android:paddingLeft="@dimen/activity_horizontal_margin"
          android:paddingRight="@dimen/activity_horizontal_margin"
          android:paddingTop="@dimen/activity_vertical_margin"
          tools:context="com.example.android.jokefactory.DisplayJokeActivity">
      
          <TextView
              android:id="@+id/joke_text"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:gravity="center"
              android:textSize="64dp"
              android:text=""
              android:layout_gravity="center_horizontal" />
      
      </RelativeLayout>

    5. I don't know why there're some errors on my activity in jokefactory

    6. According to http://stackoverflow.com/questions/30803405/cannot­resolve­symbol­appcompatactivity-­support­v7­libraries­arent­recognized​, I just need to click File → Invalidate Caches / Restart, and it did fix the problem
    7. Going back to ​MainActivity.java, replace the toast with the following content:
      public void tellJoke(View view){
              Joker joker = new Joker();
      
              // Create Intent to launch JokeFactory Activity
              Intent intent = new Intent(this, DisplayJokeActivity.class);
              // Put the string in the envelope
              intent.putExtra(getString(R.string.jokeEnvelope), joker.tellJoke());
              startActivity(intent);
          }
    8. Go to DisplayJokeActivity.java, add the following content in onCreate():

      TextView textview = (TextView) findViewById(R.id.joke_text);
      
      //Retrieve the joke from the Intent Extras
      String JokeResult = null;
      //the Intent that started us
      Intent intent = getIntent();
      JokeResult = intent.getStringExtra(getString(R.string.jokeEnvelope));
      
      if (JokeResult != null) {
           textview.setText(JokeResult);
      } else {
           textview.setText("Dig deeped, we gotta find the joke!");
      }
    9. At this point I thought I was all set to try it out, but then I came across version conflicts


    10. So I modified build.gradle (app) into like this:

      apply plugin: 'com.android.application' 
       
      android { 
          compileSdkVersion 23 
          buildToolsVersion "23.0.1" 
       
          defaultConfig { 
              applicationId "com.udacity.gradle.builditbigger" 
              minSdkVersion 15 
              targetSdkVersion 23 
              versionCode 1 
              versionName "1.0" 
          } 
          buildTypes { 
              release { 
                  minifyEnabled false 
                  proguardFiles getDefaultProguardFile('proguard­android.txt'), 
      'proguard­rules.pro' 
              }     
          } 
      } 
       
      dependencies { 
          compile fileTree(dir: 'libs', include: ['*.jar']) 
          compile 'com.android.support:appcompat­v7:23.1.1' 
          // Added for AdMob 
          compile 'com.google.android.gms:play­services:7.3.0' 
          compile project(":javajoker") 
          compile project(":jokefactory") 
      } 
    11. Build the app and try it out.
    12. You can check out the code at https://github.com/kevguy/JokeTellingApp/tree/Stage02_Android_Library 

    Step 3: Create GCE Module

    1. Right click on app and add a new Google Cloud module


    2. Fill in the blanks



      I actually changed module name to "backend" later
    3. And here we have our backend!

    4. Go to MyEndpoint.java, add the following method:

      @ApiMethod(name = "tellJoke") 
      public MyBean tellJoke(){ 
          MyBean response = new MyBean(); 
          Joker joker = new Joker(); 
          response.setData(joker.tellJoke()); 
          return response; 
      }
    5. Go back to the main app, add a new class:

    6. package com.udacity.gradle.builditbigger; 
       
      import android.content.Context; 
      import android.os.AsyncTask; 
      import android.widget.Toast; 
       
      import com.example.kev.myapplication.backend.myApi.MyApi; 
      import com.google.api.client.extensions.android.http.AndroidHttp; 
      import com.google.api.client.extensions.android.json.AndroidJsonFactory; 
      import com.google.api.client.googleapis.services.AbstractGoogleClientRequest; 
      import com.google.api.client.googleapis.services.GoogleClientRequestInitializer; 
       
      import java.io.IOException; 
       
      /** 
       * Created by kev on 3/21/16. 
       */ 
      class EndpointAsyncTask extends AsyncTask<Context, Void, String> { 
          private static MyApi myApiService = null; 
          private Context context; 
       
          @Override 
          protected String doInBackground(Context... params) { 
              if(myApiService == null) {  // Only do this once 
                  MyApi.Builder builder = new 
      MyApi.Builder(AndroidHttp.newCompatibleTransport(), 
                          new AndroidJsonFactory(), null) 
                          // options for running against local devappserver 
                          // ­ 10.0.2.2 is localhost's IP address in Android emulator 
                          // ­ turn off compression when running against local devappserver 
                          .setRootUrl("http://10.0.2.2:8080/_ah/api/") 
                          .setGoogleClientRequestInitializer(new GoogleClientRequestInitializer() { 
                              @Override                         public void initialize(AbstractGoogleClientRequest<?> 
      abstractGoogleClientRequest) throws IOException { 
                                  abstractGoogleClientRequest.setDisableGZipContent(true); 
                              } 
                          }); 
                  // end options for devappserver 
       
                  myApiService = builder.build(); 
              } 
       
              context = params[0]; 
       
       
              try { 
                  return myApiService.tellJoke().execute().getData(); 
              } catch (IOException e) { 
                  return e.getMessage(); 
              } 
          } 
       
          @Override 
          protected void onPostExecute(String result) { 
              /*// Create Intent to launch JokeFactory Activity 
              Intent intent = new Intent(context, DisplayJokeActivity.class); 
              // Put the string in the envelope 
              intent.putExtra(DisplayJokeActivity.JOKE_KEY,result); 
              context.startActivity(intent); 
      */ 
              Toast.makeText(context, result, Toast.LENGTH_LONG).show(); 
          } 
      } 
    7. Finally replace the content of tellJoke() in MainActivity.java with this:

      new EndpointAsyncTask().execute(this); 
    8. Before you start to test the app, make sure your backend is up and running (Go to Run  Run "backend"), and go to http://localhost:8080 to double check:


    9. And when you try to test it out, use an emulator instead of a real phone/tablet:


    10. And there we see our toast!
    11. You can check out the code at https://github.com/kevguy/JokeTellingApp/tree/Stage03_Local_GCE

    Step 4: Deploy App Engine

    1. Stop the backend (it it is still running locally) by selecting Run Stop
    2. Run Build → Deploy Module to App Engine
    3. A dialog will pop up


       
    4. From the "Deploy to:", click the dropdown list, and you will see another dialog:


    5. And after you sign in, you should be able to see something like this:


    6. Then you'll be brought to the Google Developers Console:


    7. Click "Create project" and enter the following:


    8. And finally you'll be brought to the dashboard with a Project ID


    9. In your project, open "src/main/webapp/WEB­INF/appengine­web.xml" and replace myApplicationId with the Project ID you got:


    10. Go back to the Dialog, click the Refresher button at the bottom right corner of the Deploy To: dropdown list and then select the project you just created


    11. When you're done, go to EndPointSyncTask.java and replace the lines

      MyApi.Builder builder = new MyApi.Builder(AndroidHttp.newCompatibleTransport(), 
      new AndroidJsonFactory(), null) 
              .setRootUrl("http://10.0.2.2:8080/_ah/api/") // 10.0.2.2 is localhost's IP address in 
      Android emulator 
              .setGoogleClientRequestInitializer(new GoogleClientRequestInitializer() { 
                  @Override 
                  public void initialize(AbstractGoogleClientRequest<?> 
      abstractGoogleClientRequest) throws IOException { 
                      abstractGoogleClientRequest.setDisableGZipContent(true); 
                  } 
              }); 
      

      with
      MyApi.Builder builder = new MyApi.Builder(AndroidHttp.newCompatibleTransport(), new 
      AndroidJsonFactory(), null) 
              .setRootUrl("https://android­app­backend.appspot.com/_ah/api/"); 
      

      where android-app-backend corresponds to your own Project ID
    12. You can check out the code at https://github.com/kevguy/JokeTellingApp/tree/Stage04_Deploy_GCE

    Step 5: Implement Progress Bar

    1. Clear the content in the tellJoke function in MainActivity.java, because we're gonna move what this function does to MainActivityFragment
    2. First we go to fragment_main.xml and add an ID to the button, and delete the onClick field so the original tellJoke() won't be triggered

      <Button 
              android:id="@+id/joke_btn" 
              android:layout_width="wrap_content" 
              android:layout_height="wrap_content" 
              android:layout_below="@+id/instructions_text_view" 
              android:text="@string/button_text" 
              /> 
      

    3. Then we add a new progress bar:

      <ProgressBar 
              android:id="@+id/joke_progressbar" 
              android:layout_width="wrap_content" 
              android:layout_height="wrap_content" 
              android:layout_centerInParent="true"/>
    4. Then we modify EndPointAsyncTask to take the fragment, add a new member variable first:

      private MainActivityFragment mainActivityFragment; 
    5. Then in doInBackground(), add the following:

      mainActivityFragment = params[0]; 
      context = mainActivityFragment.getActivity(); 
    6. Change EndPointAsyncTask to be like this:

      class EndpointAsyncTask extends AsyncTask<MainActivityFragment, Void, String> {
    7. Finally, in onPostExecute(), replace the content with the following:

      mainActivityFragment.loadedJoke = result; 
      mainActivityFragment.launchDisplayJokeActivity(); 
    8. In MainActivityFragment.java, add the following member variables:

      ProgressBar progressBar = null; 
      public String loadedJoke = null; 
    9. Then add an button OnClickListener:

      // Set onClickListener for the button 
      Button button = (Button) root.findViewById(R.id.joke_btn); 
      button.setOnClickListener(new View.OnClickListener(){ 
            @Override 
            public void onClick(View v) { 
                progressBar.setVisibility(View.VISIBLE); 
                getJoke(); 
            } 
      }); 
       
      progressBar = (ProgressBar) root.findViewById(R.id.joke_progressbar); 
      progressBar.setVisibility(View.GONE);
    10. Then add two member methods:

      public void getJoke(){ 
          new EndpointAsyncTask().execute(this); 
      } 
       
      public void launchDisplayJokeActivity(){ 
          Context context = getActivity(); 
          Intent intent = new Intent(context, DisplayJokeActivity.class); 
          intent.putExtra(context.getString(R.string.jokeEnvelope), loadedJoke); 
          //Toast.makeText(context, loadedJoke, Toast.LENGTH_LONG).show(); 
          context.startActivity(intent); 
          progressBar.setVisibility(View.GONE); 
      } 
    11. Finally, in DisplayJokeActivity, reaplace this line:

      JokeResult = intent.getStringExtra(getString(JOKE_KEY); 
      

      with
      JokeResult = intent.getStringExtra(getString(R.string.jokeEnvelope));  
    12.  Now you can build the app and try it out.
    13. Also, you can check out the code at https://github.com/kevguy/JokeTellingApp/tree/Stage05_Fragment_And_Progress_Bar

    Step 6: Function Test

    1. Go to build.gradle (app), in defaultConfig closure, add

      testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 
      

    2. In the dependencies closure, add:

      androidTestCompile 'junit:junit:4.12' 
      androidTestCompile 'com.android.support.test:runner:0.4.1' 
      androidTestCompile 'com.android.support.test:rules:0.4.1' 
      androidTestCompile 'com.android.support:support­annotations:23.2.0' 
      

    3. Inside the Android closure, add a new closure:

      dexOptions { 
              javaMaxHeapSize "4g" 
          } 
      

    4. Create a new class EndPointAsyncTaskTest in AndroidTest, with the follow content:

      package com.udacity.gradle.builditbigger;
      
      import android.support.test.runner.AndroidJUnit4;
      
      import org.junit.Test;
      import org.junit.runner.RunWith;
      
      import static org.junit.Assert.assertTrue;
      /**
       * Created by kev on 3/21/16.
       */
      
      @RunWith(AndroidJUnit4.class)
      public class EndPointAsyncTaskTest {
      
          @Test
          public void testDoInBackground() throws Exception {
              com.udacity.gradle.builditbigger.MainActivityFragment fragment = new com.udacity.gradle.builditbigger.MainActivityFragment();
              fragment.testFlag = true;
              new EndpointAsyncTask().execute(fragment);
              Thread.sleep(5000);
              assertTrue("Error: Fetched Joke = " + fragment.loadedJoke, fragment.loadedJoke != null);
          }
      }
      

    5. Then go to MainActivityFragment class, add the following member variable:

      public boolean testFlag = false; 
      

    6. Finally, wrap up everything in launchDisplayJokeActivity(), like this:

      if (!testFlag) { 
          Context context = getActivity(); 
          Intent intent = new Intent(context, DisplayJokeActivity.class); 
          intent.putExtra(context.getString(R.string.jokeEnvelope), loadedJoke); 
          //Toast.makeText(context, loadedJoke, Toast.LENGTH_LONG).show(); 
          context.startActivity(intent); 
          progressBar.setVisibility(View.GONE); 
      } 
      

    7. Build the app and check it out again, but you'll notice nothing different if everything works well
    8. You can check out the code at https://github.com/kevguy/JokeTellingApp/tree/Stage06_Functional_Test

    Step 7: Free and Paid Flavors

    1. First, in build.gradle (app), and in the Android closure, add:

      productFlavors { 
          free { 
              applicationId "com.udacity.gradle.builditbigger.free" 
              versionName "1.0­free" 
          } 
       
          paid { 
              applicationId "com.udacity.gradle.builditbigger.paid" 
              versionName "1.0­paid" 
          } 
      } 
      

    2. Our target to eliminating ads in the paid version, first we switch the build variant to paidDebug first

    3. Then we add a new MainActivityFragment for the paid version with the exact same names like  the main app, Android Studio will handle everything


    4. Our target is to eliminate the ad, so in MainActivityFragment, we'll just delete everything related to ad. FYI, here's how MainActivityFragment.java and layout_main.xml look like in the paid version:

      MainActivityFragment.java
      package com.udacity.gradle.builditbigger;
      
      import android.content.Context;
      import android.content.Intent;
      import android.os.Bundle;
      import android.support.v4.app.Fragment;
      import android.view.LayoutInflater;
      import android.view.View;
      import android.view.ViewGroup;
      import android.widget.Button;
      import android.widget.ProgressBar;
      
      import com.example.android.jokefactory.DisplayJokeActivity;
      import com.google.android.gms.ads.AdRequest;
      import com.google.android.gms.ads.AdView;
      
      
      /**
       * A placeholder fragment containing a simple view.
       */
      public class MainActivityFragment extends Fragment {
      
          public MainActivityFragment() {
          }
      
          ProgressBar progressBar = null;
          public String loadedJoke = null;
          public boolean testFlag = false;
      
          @Override
          public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                   Bundle savedInstanceState) {
              View root = inflater.inflate(R.layout.fragment_main, container, false);
      
      
              // Set onClickListener for the button
              Button button = (Button) root.findViewById(R.id.joke_btn);
              button.setOnClickListener(new View.OnClickListener(){
                  @Override
                  public void onClick(View v) {
                      progressBar.setVisibility(View.VISIBLE);
                      getJoke();
                  }
              });
      
              progressBar = (ProgressBar) root.findViewById(R.id.joke_progressbar);
              progressBar.setVisibility(View.GONE);
      
      
              return root;
          }
      
          public void getJoke(){
              new EndpointAsyncTask().execute(this);
          }
      
          public void launchDisplayJokeActivity(){
              if (!testFlag) {
                  Context context = getActivity();
                  Intent intent = new Intent(context, DisplayJokeActivity.class);
                  intent.putExtra(context.getString(R.string.jokeEnvelope), loadedJoke);
                  //Toast.makeText(context, loadedJoke, Toast.LENGTH_LONG).show();
                  context.startActivity(intent);
                  progressBar.setVisibility(View.GONE);
              }
          }
      
      }
      


      layout_main.xml
      <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
          xmlns:tools="http://schemas.android.com/tools"
          xmlns:ads="http://schemas.android.com/apk/res-auto"
          android:layout_width="match_parent"
          android:layout_height="match_parent"
          android:paddingLeft="@dimen/activity_horizontal_margin"
          android:paddingRight="@dimen/activity_horizontal_margin"
          android:paddingTop="@dimen/activity_vertical_margin"
          android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivityFragment">
      
          <TextView android:text="@string/instructions"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:id="@+id/instructions_text_view"
              />
      
          <Button
              android:id="@+id/joke_btn"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:layout_below="@+id/instructions_text_view"
              android:text="@string/button_text"
              />
      
          <ProgressBar
              android:id="@+id/joke_progressbar"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:layout_centerInParent="true"/>
      
      </RelativeLayout>

    5. Now build the app and check if there's an ad.
    6. You can check out the code at https://github.com/kevguy/JokeTellingApp/tree/Stage07_Free_Paid

    Step 8: Add Interstitial Ad

    1. This step is really simple, I just dubbed everything from https://developers.google.com/mobile-ads-sdk/docs/dfp/android/interstitial and I'm done. And yes, you have to get a Ad Unit ID first or the add won't show.
    2. FYI, here's how my MainActivityFragment.java and layout_main.xml look like:

      MainActivityFragment.java
      package com.udacity.gradle.builditbigger;
      
      import android.content.Context;
      import android.content.Intent;
      import android.os.Bundle;
      import android.support.v4.app.Fragment;
      import android.util.Log;
      import android.view.LayoutInflater;
      import android.view.View;
      import android.view.ViewGroup;
      import android.widget.Button;
      import android.widget.ProgressBar;
      
      import com.example.android.jokefactory.DisplayJokeActivity;
      import com.google.android.gms.ads.AdListener;
      import com.google.android.gms.ads.AdRequest;
      import com.google.android.gms.ads.AdView;
      import com.google.android.gms.ads.doubleclick.PublisherAdRequest;
      import com.google.android.gms.ads.doubleclick.PublisherInterstitialAd;
      import com.udacity.gradle.builditbigger.R;
      
      
      /**
       * A placeholder fragment containing a simple view.
       */
      public class MainActivityFragment extends Fragment {
      
          public MainActivityFragment() {
          }
      
          ProgressBar progressBar = null;
          public String loadedJoke = null;
          public boolean testFlag = false;
          PublisherInterstitialAd mPublisherInterstitialAd = null;
          String LOG_TAG = "FREEDUBUG";
      
          @Override
          public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                   Bundle savedInstanceState) {
      
              //Set up for pre-fetching interstitial ad request
              mPublisherInterstitialAd = new PublisherInterstitialAd(getContext());
              mPublisherInterstitialAd.setAdUnitId("ca-app-pub-7867604826748291/8122977163");
      
              mPublisherInterstitialAd.setAdListener(new AdListener() {
                  @Override
                  public void onAdClosed() {
                      super.onAdClosed();
                      //process the joke Request
                      progressBar.setVisibility(View.VISIBLE);
                      getJoke();
      
                      //pre-fetch the next ad
                      requestNewInterstitial();
                  }
      
                  @Override
                  public void onAdFailedToLoad(int errorCode) {
                      super.onAdFailedToLoad(errorCode);
      
                      Log.i(LOG_TAG, "onAdFailedToLoad: ad Failed to load. Reloading...");
      
                      //prefetch the next ad
                      requestNewInterstitial();
      
                  }
      
                  @Override
                  public void onAdLoaded() {
                      Log.i(LOG_TAG, "onAdLoaded: interstitial is ready!");
                      super.onAdLoaded();
                  }
              });
      
              //Kick off the fetch
              requestNewInterstitial();
      
      
              View root = inflater.inflate(R.layout.fragment_main, container, false);
      
              AdView mAdView = (AdView) root.findViewById(R.id.adView);
      
              // Set onClickListener for the button
              Button button = (Button) root.findViewById(R.id.joke_btn);
              button.setOnClickListener(new View.OnClickListener(){
                  @Override
                  public void onClick(View v) {
                      if (mPublisherInterstitialAd.isLoaded()) {
                          Log.i(LOG_TAG, "onClick: interstitial was ready");
                          mPublisherInterstitialAd.show();
                      } else {
                          Log.i(LOG_TAG, "onClick: interstitial was not ready.");
                          progressBar.setVisibility(View.VISIBLE);
                          getJoke();
                      }
                  }
              });
      
              progressBar = (ProgressBar) root.findViewById(R.id.joke_progressbar);
              progressBar.setVisibility(View.GONE);
      
      
      
              // Create an ad request. Check logcat output for the hashed device ID to
              // get test ads on a physical device. e.g.
              // "Use AdRequest.Builder.addTestDevice("ABCDEF012345") to get test ads on this device."
              AdRequest adRequest = new AdRequest.Builder()
                      .addTestDevice(AdRequest.DEVICE_ID_EMULATOR)
                      .build();
              mAdView.loadAd(adRequest);
              return root;
          }
      
          public void getJoke(){
              new EndpointAsyncTask().execute(this);
          }
      
          public void launchDisplayJokeActivity(){
              if (!testFlag) {
                  Context context = getActivity();
                  Intent intent = new Intent(context, DisplayJokeActivity.class);
                  intent.putExtra(context.getString(R.string.jokeEnvelope), loadedJoke);
                  //Toast.makeText(context, loadedJoke, Toast.LENGTH_LONG).show();
                  context.startActivity(intent);
                  progressBar.setVisibility(View.GONE);
              }
          }
      
      
          private void requestNewInterstitial() {
              PublisherAdRequest adRequest = new PublisherAdRequest.Builder()
                      //.addTestDevice(AdRequest.DEVICE_ID_EMULATOR)
                      .addTestDevice("EA27D37DF5448BF42AA5F7A6D4F11A9B")
                      .build();
      
              mPublisherInterstitialAd.loadAd(adRequest);
          }
      
      }
      


      layout_main.xml
      <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
          xmlns:tools="http://schemas.android.com/tools"
          xmlns:ads="http://schemas.android.com/apk/res-auto"
          android:layout_width="match_parent"
          android:layout_height="match_parent"
          android:paddingLeft="@dimen/activity_horizontal_margin"
          android:paddingRight="@dimen/activity_horizontal_margin"
          android:paddingTop="@dimen/activity_vertical_margin"
          android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivityFragment">
      
          <TextView android:text="@string/instructions"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:id="@+id/instructions_text_view"
              />
      
          <Button
              android:id="@+id/joke_btn"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:layout_below="@+id/instructions_text_view"
              android:text="@string/button_text"
              />
      
          <com.google.android.gms.ads.AdView
              android:id="@+id/adView"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:layout_centerHorizontal="true"
              android:layout_alignParentBottom="true"
              ads:adSize="BANNER"
              ads:adUnitId="@string/banner_ad_unit_id">
          </com.google.android.gms.ads.AdView>
      
          <ProgressBar
              android:id="@+id/joke_progressbar"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:layout_centerInParent="true"/>
      
      </RelativeLayout>

    3. You can check out the code at https://github.com/kevguy/JokeTellingApp/tree/Stage08_Interstitial_Ad

    Step 9: Configure Test Task

    1. Stay with me bro, we're almost done. In build.gradle, add the following closures and we're there!

      task testProject {
          dependsOn(['startDevServer','runMyTests','shutdownDevServer'])
      }
      
      task startDevServer {
          dependsOn ':backend:appengineRun'
          evaluationDependsOn(":backend")
          project(':backend').appengine.daemon = true
      }
      
      task runMyTests {
          dependsOn ':app:connectedAndroidTest'
          mustRunAfter "startDevServer"
      }
      
      task shutdownDevServer {
          dependsOn ':backend:appengineStop'
          mustRunAfter "runMyTests"
      
      }

    2. You can check out the code at https://github.com/kevguy/JokeTellingApp/tree/Stage09_Configure_Test_Task

    I finished all these things in two days, I'm sure you can do it too! Also, I'm sure you're as tired as you are, here's a pic of a Android lady warrior:

    I don't know when that name came up, but that was the exact moment I started calling Kira "Macho Nacho"


    Kev


    P.S.

    If you're here to know more about how my machine learning journey goes, I'm sorry, I'm still writing the blag post, but picking from what I left off, I've finished my first programming assignment, so stay tuned! This couple weeks I've been crazy busy doing other things: job interviews, a proxy I'd been working on for my networking course, and Android, which is what I'll be talking about this time.
    Android App: Build It Bigger! Android App: Build It Bigger! Reviewed by Kevin Lai on 9:56:00 AM Rating: 5

    3 comments:

    1. Great tutorial! I stopped by looking for info on paid/free flavors. For your async test, may I suggest using CountDownLatch instead of Thread.sleep. I found out this is a good way to test async tasks. Also, I used my TinyEvent lib to decouple the fragment from the async task. Here's my EndPointAsyncTaskTest and repo in case it is useful somehow.

      https://github.com/rgr-myrg/udacity/blob/develop/android/build-it-bigger/app/src/androidTest/java/com/udacity/gradle/builditbigger/EndPointAsyncTaskTest.java

      Best.

      ReplyDelete

    2. Thanks for sharing this useful information! I am very happy to read this article
      https://www.drozus.com

      ReplyDelete
    3. If you a have question like that how to build an app? Then you should know that there are some popular names like Weebly, Wix, and Squarespace which let you create a website with just a drag-and-drop option. While using sites like Woocommerce and Shopify, you can easily develop a web store in no time.

      ReplyDelete

    Powered by Blogger.