Using Box Java SDK in Android app with OAuth 2.0 — Part 2

Mateusz Woda
Box Developer Blog
Published in
9 min readApr 11, 2023

In my previous blog post, I explained how to authenticate with Box API using OAuth 2.0. Although the application used the Box Java SDK, it didn’t use all of its capabilities. In this post, I will demonstrate how to receive data from Box and show it in a user-friendly way on a mobile device.

Prerequisites: Box developer account, Java (minimum required version for Box Java SDK is Java 8), Android Studio (not mandatory, but will make things easier), working app from previous blog post.

Call Box API using SDK and map response to models

In the demo code described in the last article ItemsAcitivity was created just to display OAuth code received from the Box API. This time you will use this activity to list the items located in the root folder of your Box account and display them on the screen.

First, let’s create models in the Android app to map responses returned from the box-java-sdk in order to decouple your app from external dependency.

Let’s create some classes for this purpose.

  1. Create a Models package under root package.
  2. Create a class called Item. This class should contain name and modified_by fields.
package com.example.boxjavasdkinandroid.Models;

public class Item {
private final String name;
private final String modifiedBy;

public Item(String name, String modifiedBy) {
this.name = name;
this.modifiedBy = modifiedBy;
}

public String getName() {
return name;
}

public String getModifiedBy() {
return modifiedBy;
}
}

3. Create a class called File that inherits from Item class.

package com.example.boxjavasdkinandroid.Models;

public class File extends Item {
public File(String name, String modifiedBy) {
super(name, modifiedBy);
}
}

4. Create a class called Folder that inherits from Item class.

package com.example.boxjavasdkinandroid.Models;

public class Folder extends Item {
public Folder(String name, String modifiedBy) {
super(name, modifiedBy);
}
}

In this scenario, both File and Folder models contain identical data. They will only required be used to show them with different icons on the UI. As your application grows, these models may contain different data that can be displayed in different ways.

You can use BoxAPIConnection from the box-java-sdk constructor to create a Box API http/s client, and then pass it to BoxFolder class. By calling getChildren on BoxFolder, you can get an iterator for all items present in a folder (in this case root folder with id 0).

BoxAPIConnection api = new BoxAPIConnection(getString(R.string.client_id),
getString(R.string.client_secret), code);
Iterable<BoxItem.Info> boxFolder = new BoxFolder(api, "0")
.getChildren("name", "modified_by");

name and modified_by fields were explicitly passed to the box-java-sdk function to tell it to include these fields in the response.

To map the models from the box-java-sdk to app domain, let’s create a separate function called getFolderItems in ItemsActivity, which will consume the Iterable returned from the box-java-sdk and map the response depending on the type returned.

private List<Item> getFolderItems(String code) {
BoxAPIConnection api = new BoxAPIConnection(getString(R.string.client_id),
getString(R.string.client_secret), code);
Iterable<BoxItem.Info> boxFolder = new BoxFolder(api, "0")
.getChildren("name", "modified_by");

List<Item> items = new ArrayList<>();
for (BoxItem.Info itemInfo : boxFolder) {
if (itemInfo instanceof BoxFile.Info) {
BoxFile.Info fileInfo = (BoxFile.Info) itemInfo;
File file = new File(fileInfo.getName(), fileInfo.getModifiedBy().getName());
items.add(file);
} else if (itemInfo instanceof BoxFolder.Info) {
BoxFolder.Info folderInfo = (BoxFolder.Info) itemInfo;
Folder folder = new Folder(folderInfo.getName(), folderInfo.getModifiedBy().getName());
items.add(folder);
}
}
return items;
}

⚠️ Note: Keep in mind that iterators returned from the box-java-sdk allow you to iterate over ALL records using pagination. If the iterator encounters end of the page (expressed by the limit parameter), it will make another http/s call to the Box API. This may result in a several API calls, which may be unnecessary if you plan to display only part of the data to the user. Consider a design that minimizes the number of http/s calls, as this can be costly and affect the performance of your application.

Create layout

The UI is an important part of any application. It should clearly communicate the developer’s intention to the user. Let’s try to create a UI that clearly distinguishes between the different types of items returned from the API Box.

In Android Studio, right click on the res folder and select New -> Vector Asset to add a new icon. Select the clip art named folder and change it’s size to 64x64dp. Set the name to ic_folder_64. Set the color to #fdb900.

For the File model, choose insert file drive clip art and set it to 64x64dp once again. Set the name to ic_file_64. Set the color to #d3d3d3.

Now let’s modify layout a bit. You will utilize the RecyclerView, which you will create in the next step. For now, let’s focus on the view itself.

Add list_item.xml file under res/layout directory. It will represent a single item in the list, displayed with icon, name and modified_by fields.

list_item.xml should look like this

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">

<ImageView
android:id="@+id/item_image_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:src="@drawable/ic_folder_64"
android:padding="10dp" />

<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center_vertical">

<TextView
android:id="@+id/item_name_text_view"
android:layout_width="wrap_content"
android:textSize="20sp"
android:layout_height="wrap_content"
android:textColor="@color/black"
tools:text="Name" />

<TextView
android:id="@+id/modified_by_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="Modified By" />

</LinearLayout>
</LinearLayout>

Now let’s modify activity_items.xml layout. It should contain a RecyclerView and a ProgressBar in the form of a spinner, which will be displayed when retrieving records from the Box API.

activity_items.xml should look like this.

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">

<ProgressBar
android:id="@+id/progress_bar"
style="?android:progressBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />

<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view_items"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="vertical"
app:layoutManager="LinearLayoutManager" />
</FrameLayout>

Create RecyclerView

RecyclerView makes it easier to create dynamic lists without loading everything into the memory, making it more responsive. To use it, let’s implement an Adapter and a ViewHolder. The ViewHolder contains a representation of a single item, while the Adapter creates ViewHolder objects as needed.

The ItemViewHolder should include name and modified_by fields. It should also contain an icon that will be displayed on the UI depending on the type of an item.

public class ItemViewHolder extends RecyclerView.ViewHolder {
public TextView nameTextView;
public TextView modifiedByTextView;
public ImageView imageView;

public ItemViewHolder(@NonNull View itemView) {
super(itemView);
nameTextView = itemView.findViewById(R.id.item_name_text_view);
modifiedByTextView = itemView.findViewById(R.id.modified_by_text_view);
imageView = itemView.findViewById(R.id.item_image_view);
}
}

The most important logic will be contained inside onBindViewHolder. Inside it, the appropriate icon will be set depending on the type — either File or Folder.

@Override
public void onBindViewHolder(@NonNull ItemViewHolder holder, int position) {
Item item = items.get(position);
holder.nameTextView.setText(item.getName());
holder.modifiedByTextView.setText(item.getModifiedBy());
if (item instanceof File) {
holder.imageView.setImageResource(R.drawable.ic_file_64);
} else if (item instanceof Folder) {
holder.imageView.setImageResource(R.drawable.ic_folder_64);
}
}

Full code for ItemAdapter should look like this.

package com.example.boxjavasdkinandroid;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import com.example.boxjavasdkinandroid.Models.File;
import com.example.boxjavasdkinandroid.Models.Folder;
import com.example.boxjavasdkinandroid.Models.Item;

import java.util.List;

public class ItemAdapter extends RecyclerView.Adapter<ItemAdapter.ItemViewHolder> {
private final List<Item> items;

public ItemAdapter(List<Item> items) {
this.items = items;
}

@NonNull
@Override
public ItemViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
View folderView = inflater.inflate(R.layout.list_item, parent, false);

return new ItemViewHolder(folderView);
}

@Override
public void onBindViewHolder(@NonNull ItemViewHolder holder, int position) {
Item item = items.get(position);
holder.nameTextView.setText(item.getName());
holder.modifiedByTextView.setText(item.getModifiedBy());
if (item instanceof File) {
holder.imageView.setImageResource(R.drawable.ic_file_64);
} else if (item instanceof Folder) {
holder.imageView.setImageResource(R.drawable.ic_folder_64);
}
}

@Override
public int getItemCount() {
return items.size();
}

public class ItemViewHolder extends RecyclerView.ViewHolder {
public TextView nameTextView;
public TextView modifiedByTextView;
public ImageView imageView;

public ItemViewHolder(@NonNull View itemView) {
super(itemView);
nameTextView = itemView.findViewById(R.id.item_name_text_view);
modifiedByTextView = itemView.findViewById(R.id.modified_by_text_view);
imageView = itemView.findViewById(R.id.item_image_view);
}
}
}

Finish ItemsActivity logic

Now it’s time to wrap up all the work done in the previous step. Open ItemsActivity file.

Let’s introduce two functions. One that will show ProgressBar and hide the RecyclerView. It will be used when loading data. Second one will do the opposite — it will hide the ProgressBar and show all items when they are ready.

private void showProgressBar(ProgressBar progressBar, RecyclerView recyclerView) {
recyclerView.setVisibility(View.GONE);
progressBar.setVisibility(View.VISIBLE);
}

private void showData(ProgressBar progressBar, RecyclerView recyclerView) {
progressBar.setVisibility(View.GONE);
recyclerView.setVisibility(View.VISIBLE);
}

Updating the UI can take some time as data is fetched from the network. By default, network calls are not allowed on the main Android thread, as they could take a long time, freeze the UI and thus confuse the user. Network calls should be performed on a separate thread, so the data can be updated asynchronously.

Let’s introduce a new function in ItemsActivity called updateUI, which will use the previously created getFolderItems function on a separate thread and update the UI when data is received from the Box API.

private void updateUI(String code, ProgressBar progressBar, RecyclerView recyclerView) {
ExecutorService executor = Executors.newSingleThreadExecutor();
Handler handler = new Handler(Looper.getMainLooper());

executor.execute(() -> {
List<Item> items = getFolderItems(code);
handler.post(() -> {
recyclerView.setAdapter(new ItemAdapter(items));
showData(progressBar, recyclerView);
});
});
}

This function uses the java.util.concurrent package namely Executor and Handler to make http/s calls on a separate thread. When the data is ready showData is called to show the RecyclerView and hide the ProgressBar.

What remains to be done is to complete the logic in the onCreate function so that updateUI is called when the code from OAuth is present.

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_items);
ProgressBar progressBar = findViewById(R.id.progress_bar);
RecyclerView recyclerView = findViewById(R.id.recycler_view_items);
recyclerView.addItemDecoration(new DividerItemDecoration(recyclerView.getContext(),
DividerItemDecoration.VERTICAL));
showProgressBar(progressBar, recyclerView);

Bundle extras = getIntent().getExtras();
if (extras != null) {
String code = extras.getString("code");
if (!code.isEmpty()) {
updateUI(code, progressBar, recyclerView);
}
// in case we don't get any code we should display an error
}
}

Here is the finished ItemsActivity code.

package com.example.boxjavasdkinandroid;

import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.View;
import android.widget.ProgressBar;

import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.RecyclerView;

import com.box.sdk.BoxAPIConnection;
import com.box.sdk.BoxFile;
import com.box.sdk.BoxFolder;
import com.box.sdk.BoxItem;
import com.example.boxjavasdkinandroid.Models.File;
import com.example.boxjavasdkinandroid.Models.Folder;
import com.example.boxjavasdkinandroid.Models.Item;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ItemsActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_items);
ProgressBar progressBar = findViewById(R.id.progress_bar);
RecyclerView recyclerView = findViewById(R.id.recycler_view_items);
recyclerView.addItemDecoration(new DividerItemDecoration(recyclerView.getContext(),
DividerItemDecoration.VERTICAL));
showProgressBar(progressBar, recyclerView);

Bundle extras = getIntent().getExtras();
if (extras != null) {
String code = extras.getString("code");
if (!code.isEmpty()) {
updateUI(code, progressBar, recyclerView);
}
// in case we don't get any code we should display an error
}
}

private void updateUI(String code, ProgressBar progressBar, RecyclerView recyclerView) {
ExecutorService executor = Executors.newSingleThreadExecutor();
Handler handler = new Handler(Looper.getMainLooper());

executor.execute(() -> {
List<Item> items = getFolderItems(code);
handler.post(() -> {
recyclerView.setAdapter(new ItemAdapter(items));
showData(progressBar, recyclerView);
});
});
}

private void showProgressBar(ProgressBar progressBar, RecyclerView recyclerView) {
recyclerView.setVisibility(View.GONE);
progressBar.setVisibility(View.VISIBLE);
}

private void showData(ProgressBar progressBar, RecyclerView recyclerView) {
progressBar.setVisibility(View.GONE);
recyclerView.setVisibility(View.VISIBLE);
}

private List<Item> getFolderItems(String code) {
BoxAPIConnection api = new BoxAPIConnection(getString(R.string.client_id),
getString(R.string.client_secret), code);
Iterable<BoxItem.Info> boxFolder = new BoxFolder(api, "0")
.getChildren("name", "modified_by");

List<Item> items = new ArrayList<>();
for (BoxItem.Info itemInfo : boxFolder) {
if (itemInfo instanceof BoxFile.Info) {
BoxFile.Info fileInfo = (BoxFile.Info) itemInfo;
File file = new File(fileInfo.getName(), fileInfo.getModifiedBy().getName());
items.add(file);
} else if (itemInfo instanceof BoxFolder.Info) {
BoxFolder.Info folderInfo = (BoxFolder.Info) itemInfo;
Folder folder = new Folder(folderInfo.getName(), folderInfo.getModifiedBy().getName());
items.add(folder);
}
}
return items;
}
}

Run the application

Launch the application. Go through the authorization process. When you are redirected back to the application you should see spinner for a while.

Note that the UI is not frozen because the data is fetched on a separate thread. When response from the Box API is received you should see all the items in your root folder.

That’s it! I will let you discover what box-java-sdk has to offer on your own. And believe me, there are many things you can achieve with it!

If you are planning to create a modern Android app from the scratch, I recommend trying out Kotlin with Jetpack Compose, as this is currently the preferred approach for new apps. Box Java SDK also works with Kotlin-based apps! Check out our example. This can be achieved through Kotlin interopability with Java.

Source code: https://github.com/box-community/android-oauth-with-sdk/tree/main/part2

Want to learn more? Visit our developer documentation site. Feel free to reach out to us on the developer forum for support or via Box Pulse to make suggestions on how to improve the Box developer experience.

Follow our Box Developers profile on Twitter to stay up-to-date.

Cheers!

--

--