Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Call Firebase database from Google App Engine

I followed this tutorial for setting up my Google App Engine instance and I am also using Firebase. My goal is to put all of the "computation" on Google App Engine. I want to call a function like this one below:

MyEndpoint:

package productions.widowmaker110.backend;

/** An endpoint class we are exposing */
@Api(
 name = "myApi",
 version = "v1",
 namespace = @ApiNamespace(
 ownerDomain = "backend.widowmaker110.productions",
 ownerName = "backend.widowmaker110.productions",
 packagePath=""
 )
)
public class MyEndpoint {

 /** A simple endpoint method that takes a name and says Hi back */
 @ApiMethod(name = "sayHi")
 public MyBean sayHi(@Named("name") String name) {

 // Write a message to the database
 FirebaseDatabase database = FirebaseDatabase.getInstance();
 DatabaseReference myRef = database.getReference("message");

 // Read from the database
 myRef.addValueEventListener(new ValueEventListener() {
 @Override
 public void onDataChange(DataSnapshot dataSnapshot) {
 // This method is called once with the initial value and again
 // whenever data at this location is updated.
 String value = dataSnapshot.getValue(String.class);
 Log.d(TAG, "Value is: " + value);
 }

 @Override
 public void onCancelled(DatabaseError error) {
 // Failed to read value
 Log.w(TAG, "Failed to read value.", error.toException());
 }
 });

 MyBean response = new MyBean();
 response.setData("Hi, " + name);

 return response;
 }

}

MainActivity:

package productions.widowmaker110.gpsweather;

// imports...

public class MainActivity extends AppCompatActivity {

 @Override
 protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    new EndpointsAsyncTask().execute(new Pair<Context, String>(this, "Manfred"));
 }

 class EndpointsAsyncTask extends AsyncTask<Pair<Context, String>, Void, String> {
    private MyApi myApiService = null;
  private Context context;

 @Override
 protected String doInBackground(Pair<Context, String>... 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].first;
 String name = params[0].second;

 try {
 return myApiService.sayHi(name).execute().getData();
 } catch (IOException e) {
 return e.getMessage();
 }
 }

 @Override
 protected void onPostExecute(String result) {
 Toast.makeText(context, result, Toast.LENGTH_LONG).show();
 }
 }
}

I understand the above code for Firebase is Android specific so running on a Google App Engine instance doesn't work. I was wondering if any one knows how to perform CRUD operations on a firebase database from Google App Engine backend. Any help is appreciated.

like image 849
booky99 Avatar asked Jun 18 '16 01:06

booky99


1 Answers

You'll need to use the Firebase Server SDK to make server side calls. You can find info on setting that up and using it here:

Add Firebase to your Server

Firebase Server SDK Installation & Setup

When using Firebase with Google Cloud Endpoints be aware that you'll need to use Firebase methods in conjunction with the Tasks API. Since Firebase methods are not blocking, if you don't use Tasks your Endpoint will return before the call you made to Firebase has a chance to return its result. For a brief intro on using tasks check out the link below. Its a talk given at Google I/O 2016. The speaker is talking about Tasks and Firebase on Android but the concepts are the same when using Tasks and Firebase on a server. Note that they have included the Tasks API with the Firebase Server SDK. I've skipped to the portion of the talk which deals directly with tasks.

Firebase SDK for Android: A tech deep dive

The samples below are if you need to process the result from your Firebase read/write operation before your Endpoint returns a value or if other code depends on the result of the Firebase read/write operation. These are server-side examples. I’m going to assume you care whether or not the write operation was successful. There may be better ways to perform these server-side operations but this is what I’ve done so far.

Sample write operation using setValue():

DatabaseReference ref = FirebaseDatabase.getInstance().getReference();
YourModelClass obj = new YourModelClass();
  1. When a call is made to the setValue() method it returns a Task object, keep a reference to it.

    Task<Void> setValueTask = ref.setValue(obj);
    
  2. Create a TaskCompletionSource object. This object should be parameterized with the result type of your choice. I’m going to use Boolean as the result type for this example.

    final TaskCompletionSource<Boolean> tcs = new TaskCompletionSource<>();
    
  3. Generate a Task from the TaskCompletionSource that was created in step 2. Again, the generated Task should use the same parameter type as the TaskCompletionSource object.

    Task<Boolean> tcsTask = tcs.getTask();
    
  4. Add a completion listener to the Task that was generated by the call to setValue(). In the completion listener set the appropriate result on the Task created in step 3. Calling setResult() on your TaskCompletionSouce object will mark the Task created from it as complete. This is important for step 5.

    setValueTask.addOnCompleteListener(new OnCompleteListener<Void>() {
       @Override
       public void onComplete(@NonNull Task<Void> task) {
          if(task.isSuccessful()){
             tcs.setResult(true);
          }else{
             tcs.setResult(false);
          }
       }
    });
    
  5. Call Task.await() to block the current thread until the Task you are interested in has completed. We are waiting for the Task generated by the TaskCompletionSource object to be marked complete. This Task will be considered complete when we call setResult() on the TaskCompletionSource used to generate the Task as we did in step 4. Once complete it will return the result.

    try {
        Boolean result = Tasks.await(tcsTask);
    }catch(ExecutionException e){
        //handle exception 
    }catch (InterruptedException e){
        //handle exception
    }
    

That’s it, the current thread will block until Tasks.await() returns a value. You can also (and should) set a timeout value on the Tasks.await() method if you want to keep the current thread from being blocked indefinitely.

If you were only interested in whether or not the Task generated by setValue() completed and didn’t care if it was successful or not then you can skip the creation of the TaskCompletionSource and just use Tasks.await() directly on that Task. The same works for updateChildren(). And if you like you can just use the method calls for updateChilden() or setValue() which use a DatabaseReference.CompletionListener along with a TaskCompletionListener.

Waiting for a read operation to complete is similar.

Sample read operation using addListenerForSingleValueEvent()

DatabaseReference ref = FirebaseDatabase.getInstance().getReference();
YourModelClass mModelClassObject;
  1. Create a TaskCompletionSource object parameterized with the result you expect from the Task that will be generated from it.

    final TaskCompletionSource<YourModelClass> tcs = new TaskCompletionSource<>();
    
  2. Generate a Task from the TaskCompletionSource object

    Task<YourModelClass> tcsTask = tcs.getTask();
    
  3. Call addListenerForSingleValueEvent() on our DatabaseReference and call setResult() on the Task generated in step 2.

    ref.addListenerForSingleValueEvent(new ValueEventListener() {
       @Override
       public void onDataChange(DataSnapshot dataSnapshot) {
            YourModelClass result = dataSnapshot.getValue(YourModelClass.class);
            if(result != null){
                tcs.setResult(result);
            }
       }
    
       @Override
       public void onCancelled(DatabaseError databaseError){
            //handle error
       }
    });
    
  4. Call Tasks.await() to block the current thread until the Task you are interested in has completed. The Task will be considered complete when we call setResult() as we did in step 3 and will return the result.

    try {
        mModelClassObject = Tasks.await(tcsTask);
    }catch(ExecutionException e){
        //handle exception 
    }catch (InterruptedException e){
        //handle exception
    }
    

As mentioned above you can use the Tasks.await() method along with a timeout value to prevent the current thread from being blocked indefinitely.

Just as a heads up I've found that Firebase doesn't kill the background thread that is uses for its operations. This means that the GAE Instance is never idle. Check out this thread for more info:

Firebase, Background Threads, and App Engine

like image 66
Kevin O'Neil Avatar answered Nov 03 '22 05:11

Kevin O'Neil