Lesson Weekend

In the previous lesson we successfully wrote small strings of data to Firebase. However, we eventually want our MyRestaurants users to save entire restaurants to their own personalized lists. Our restaurants are complex Java objects, not just string values. In this lesson we'll cover writing Java objects to Firebase, and implement functionality to allow users to save restaurants to their own personal list.

Object Formatting in Firebase

Firebase makes it rather simple to read and write POJOs (plain old Java objects) with its built-in serialization capabilities. We just need to adhere to a few simple rules:

  • Each variable name must match the name of the keys of our children nodes.

  • Each member variable must be a valid JSON type. ArrayList is not a valid JSON type, so we need to change our address and categories variables to List types.

  • We must provide an empty constructor.

  • We must also provide public getter methods for every member variable.

Conveniently enough, we already have an empty Restaurant constructor created for use by the Parceler library, and public getter methods. We'll just need to turn our ArrayList items into List items instead. Make sure your Restaurant class looks like this:

Restaurant.java
...
import java.util.List;

@Parcel
public class Restaurant {
    String name;
    String phone;
    String website;
    double rating;
    String imageUrl;
    List<String> address = new ArrayList<>();
    double latitude;
    double longitude;
    List<String> categories = new ArrayList<>();

    public Restaurant() {}

    public Restaurant(String name, String phone, String website, double rating, String imageUrl, ArrayList<String> address, double latitude, double longitude, ArrayList<String> categories) {
        this.name = name;
        this.phone = phone;
        this.website = website;
        this.rating = rating;
        this.imageUrl = imageUrl;
        this.address = address;
        this.latitude = latitude;
        this.longitude = longitude;
        this.categories = categories;
    }

    public String getName() {
        return name;
    }

    public String getPhone() {
        return phone;
    }

    public String getWebsite() {
        return website;
    }

    public double getRating() {
        return rating;
    }

    public String getImageUrl() {
        return imageUrl;
    }

    public List<String> getAddress() {
        return address;
    }

    public double getLatitude() {
        return latitude;
    }

    public double getLongitude() {
        return longitude;
    }

    public List<String> getCategories() {
        return categories;
    }
}

As you can see, we've changed the address and categories variables to be List type, including within the getAddress() and getCategories() getter methods.

Note: If you receive failed to bounce type errors, double-check that public getter methods exist for each variable and that each is a valid JSON type.

Saving Objects

Now that we’ve altered our Restaurant class to adhere to Firebase's rules, let’s begin saving restaurants. Remember, we want users to be able to use our app to search for restaurants in a provided area, and save any restaurants they're interested in to their own customizable list.

We should already have a "Save Restaurant" button in our restaurant detail view. Let's configure our application to actually save a restaurant to our database when a user clicks this option:

save-restaurant-button-in-detail-view

First, we'll add the restaurants node's key name (which will serve as the key in our data's key-value relationship) to our Constants class, just like we did for our searchedLocation node in previous lessons.

Remember, this node doesn't exist yet, but Firebase will create it when it doesn't find an existing node with this name. Moving forward, we should always save the node names as constants in Constants.java.

Constants.java
public class Constants {
    ...
    public static final String FIREBASE_CHILD_RESTAURANTS = "restaurants";
}

Now, we'll attach a click listener to our "Save Restaurants" button, and add code to our onClick() method that will save a restaurant to our restaurants node in Firebase when the button is selected:

RestaurantDetailFragment.java
public class RestaurantDetailFragment extends Fragment implements View.OnClickListener {
    ...

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_restaurant_detail, container, false);
        ButterKnife.bind(this, view);

        Picasso.with(view.getContext())
                .load(mRestaurant.getImageUrl())
                .resize(MAX_WIDTH, MAX_HEIGHT)
                .centerCrop()
                .into(mImageLabel);

        mNameLabel.setText(mRestaurant.getName());
        mCategoriesLabel.setText(android.text.TextUtils.join(", ", mRestaurant.getCategories()));
        mRatingLabel.setText(Double.toString(mRestaurant.getRating()) + "/5");
        mPhoneLabel.setText(mRestaurant.getPhone());
        mAddressLabel.setText(android.text.TextUtils.join(", ", mRestaurant.getAddress()));

        mWebsiteLabel.setOnClickListener(this);
        mPhoneLabel.setOnClickListener(this);
        mAddressLabel.setOnClickListener(this);

        mSaveRestaurantButton.setOnClickListener(this);

        return view;
    }

    @Override
    public void onClick(View v) {
    ...
        if (v == mSaveRestaurantButton) {
            DatabaseReference restaurantRef = FirebaseDatabase
                    .getInstance()
                    .getReference(Constants.FIREBASE_CHILD_RESTAURANTS);
            restaurantRef.push().setValue(mRestaurant);
            Toast.makeText(getContext(), "Saved", Toast.LENGTH_SHORT).show();
        }
    }
}

In the above code, we do the following:

  • Set a click listener for mSaveRestaurantButton in the fragment's existing onCreateView() method.

  • Add another conditional statement to the View.OnClickListener interface's onClick() override (we should already have several conditionals that create implicit intents if the user clicks on an address, phone number, or website, as covered in this lesson).

  • In the conditional, we create a new DatabaseReference object called restaurantRef using the getInstance() and getReference() methods, passing in the key for our restaurants node.

  • Then, we call push() and setValue() , passing in our restaurant object as an argument, to create a node for the selected restaurant with a unique push id.

  • Finally, we display a brief toast to confirm the restaurant has been saved.

Now, if we run our app, we should be able to click the Save Restaurant button, navigate to our Firebase app's Data tab and see all of the data pertaining to the selected restaurant!

pojo-visible-in-firebase-dashboard


Example GitHub Repo for MyRestaurants

Terminology


  • POJOs: Stands for "Plain old Java objects".

Overview


Writing Java Objects to Firebase

In order to read and write POJOS to FIrebase, we must adhere to the following rules:

  • Each member variable name must match the name of the keys of children nodes.
  • Each member variable must be a valid JSON type.
  • An empty constructor must be provided.
  • Public getter methods for every member variable must be included.

Examples