# A simple offline map

> **Note (legacy): A newer version of the Maps SDK is available**
> 
> This page uses v9.7.1 of the Mapbox Maps SDK. A newer version of the SDK is available. Learn about the latest version, v11.31.0, in the [Maps SDK documentation](https://docs.mapbox.com/android/maps/guides/).

Your browser doesn't support HTML5 video. Open [link to the video](https://docs.mapbox.com/android/android/assets/medias/maps-example-simple-offline-activity-3c8d60ff155d369c61fb1e4135f77fe8.mp4).

> **Note**
> 
> This example is a part of the [Mapbox Android Demo app](https://github.com/mapbox/mapbox-android-demo). You can find the values for all referenced resources in the [`res`](https://github.com/mapbox/mapbox-android-demo/tree/master/MapboxAndroidDemo/src/main/res) directory. For example, see [`res/values/activity_strings.xml`](https://github.com/mapbox/mapbox-android-demo/tree/master/MapboxAndroidDemo/src/main/res/values/activity_strings.xml) for `R.string.*` references used in this example. The dependencies can be found [here.](https://github.com/mapbox/mapbox-navigation-android-examples/blob/main/app/build.gradle) The examples use View binding. [See setup documention if necessary.](https://developer.android.com/topic/libraries/view-binding)

Title: `activity_offline_simple`

[View on GitHub](https://github.com/mapbox/mapbox-android-demo/tree/master/MapboxAndroidDemo/src/main/res/layout/activity_offline_simple.xml)

```xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:mapbox="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".examples.offline.SimpleOfflineMapActivity">

    <com.mapbox.mapboxsdk.maps.MapView
        android:id="@+id/mapView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        mapbox:mapbox_cameraTargetLat="37.73359"
        mapbox:mapbox_cameraTargetLng="-119.58410"
        mapbox:mapbox_cameraZoom="10"
        mapbox:mapbox_cameraZoomMin="10"/>

    <ProgressBar
        android:id="@+id/progress_bar"
        style="?android:attr/progressBarStyleHorizontal"
        android:layout_width="match_parent"
        android:layout_height="25dp"
        android:paddingLeft="25dp"
        android:layout_gravity="center"
        android:paddingRight="25dp"
        android:visibility="gone"/>

</FrameLayout>
```

**Java**

Title: `SimpleOfflineMapActivity.java`

[View on GitHub](https://github.com/mapbox/mapbox-android-demo/tree/master/MapboxAndroidDemo/src/main/java/com/mapbox/mapboxandroiddemo/examples/offline/SimpleOfflineMapActivity.java)

```java
package com.mapbox.mapboxandroiddemo.examples.offline;

import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.Toast;

import com.mapbox.mapboxandroiddemo.R;
import com.mapbox.mapboxsdk.Mapbox;
import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.geometry.LatLngBounds;
import com.mapbox.mapboxsdk.maps.MapView;
import com.mapbox.mapboxsdk.maps.MapboxMap;
import com.mapbox.mapboxsdk.maps.OnMapReadyCallback;
import com.mapbox.mapboxsdk.maps.Style;
import com.mapbox.mapboxsdk.offline.OfflineManager;
import com.mapbox.mapboxsdk.offline.OfflineRegion;
import com.mapbox.mapboxsdk.offline.OfflineRegionError;
import com.mapbox.mapboxsdk.offline.OfflineRegionStatus;
import com.mapbox.mapboxsdk.offline.OfflineTilePyramidRegionDefinition;

import org.json.JSONObject;

import timber.log.Timber;

/**
 * Download and view an offline map using the Mapbox Android SDK.
 */
public class SimpleOfflineMapActivity extends AppCompatActivity {

  private boolean isEndNotified;
  private ProgressBar progressBar;
  private MapView mapView;
  private OfflineManager offlineManager;

  // JSON encoding/decoding
  public static final String JSON_CHARSET = "UTF-8";
  public static final String JSON_FIELD_REGION_NAME = "FIELD_REGION_NAME";

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Mapbox access token is configured here. This needs to be called either in your application
    // object or in the same activity which contains the mapview.
    Mapbox.getInstance(this, getString(R.string.access_token));

    // This contains the MapView in XML and needs to be called after the access token is configured.
    setContentView(R.layout.activity_offline_simple);

    mapView = findViewById(R.id.mapView);
    mapView.onCreate(savedInstanceState);
    mapView.getMapAsync(new OnMapReadyCallback() {
      @Override
      public void onMapReady(@NonNull final MapboxMap mapboxMap) {

        mapboxMap.setStyle(Style.OUTDOORS, new Style.OnStyleLoaded() {
          @Override
          public void onStyleLoaded(@NonNull Style style) {

            // Set up the OfflineManager
            offlineManager = OfflineManager.getInstance(SimpleOfflineMapActivity.this);

            // Create a bounding box for the offline region
            LatLngBounds latLngBounds = new LatLngBounds.Builder()
              .include(new LatLng(37.7897, -119.5073)) // Northeast
              .include(new LatLng(37.6744, -119.6815)) // Southwest
              .build();

            // Define the offline region
            OfflineTilePyramidRegionDefinition definition = new OfflineTilePyramidRegionDefinition(
              style.getUri(),
              latLngBounds,
              10,
              20,
              SimpleOfflineMapActivity.this.getResources().getDisplayMetrics().density);

            // Set the metadata
            byte[] metadata;
            try {
              JSONObject jsonObject = new JSONObject();
              jsonObject.put(JSON_FIELD_REGION_NAME, "Yosemite National Park");
              String json = jsonObject.toString();
              metadata = json.getBytes(JSON_CHARSET);
            } catch (Exception exception) {
              Timber.e("Failed to encode metadata: %s", exception.getMessage());
              metadata = null;
            }

            // Create the region asynchronously
            if (metadata != null) {
              offlineManager.createOfflineRegion(
                definition,
                metadata,
                new OfflineManager.CreateOfflineRegionCallback() {
                  @Override
                  public void onCreate(OfflineRegion offlineRegion) {
                    offlineRegion.setDownloadState(OfflineRegion.STATE_ACTIVE);

                    // Display the download progress bar
                    progressBar = findViewById(R.id.progress_bar);
                    startProgress();

                    // Monitor the download progress using setObserver
                    offlineRegion.setObserver(new OfflineRegion.OfflineRegionObserver() {
                      @Override
                      public void onStatusChanged(OfflineRegionStatus status) {

                        // Calculate the download percentage and update the progress bar
                        double percentage = status.getRequiredResourceCount() >= 0
                          ? (100.0 * status.getCompletedResourceCount() / status.getRequiredResourceCount()) :
                          0.0;

                        if (status.isComplete()) {
                          // Download complete
                          endProgress(getString(R.string.simple_offline_end_progress_success));
                        } else if (status.isRequiredResourceCountPrecise()) {
                          // Switch to determinate state
                          setPercentage((int) Math.round(percentage));
                        }
                      }

                      @Override
                      public void onError(OfflineRegionError error) {
                        // If an error occurs, print to logcat
                        Timber.e("onError reason: %s", error.getReason());
                        Timber.e("onError message: %s", error.getMessage());
                      }

                      @Override
                      public void mapboxTileCountLimitExceeded(long limit) {
                        // Notify if offline region exceeds maximum tile count
                        Timber.e("Mapbox tile count limit exceeded: %s", limit);
                      }
                    });
                  }

                  @Override
                  public void onError(String error) {
                    Timber.e("Error: %s", error);
                  }
                });
            }
          }
        });
      }
    });
  }

  @Override
  public void onResume() {
    super.onResume();
    mapView.onResume();
  }

  @Override
  protected void onStart() {
    super.onStart();
    mapView.onStart();
  }

  @Override
  protected void onStop() {
    super.onStop();
    mapView.onStop();
  }

  @Override
  public void onPause() {
    super.onPause();
    mapView.onPause();
    if (offlineManager != null) {
      offlineManager.listOfflineRegions(new OfflineManager.ListOfflineRegionsCallback() {
        @Override
        public void onList(OfflineRegion[] offlineRegions) {
          if (offlineRegions.length > 0) {
            // delete the last item in the offlineRegions list which will be yosemite offline map
            offlineRegions[(offlineRegions.length - 1)].delete(new OfflineRegion.OfflineRegionDeleteCallback() {
              @Override
              public void onDelete() {
                Toast.makeText(
                  SimpleOfflineMapActivity.this,
                  getString(R.string.basic_offline_deleted_toast),
                  Toast.LENGTH_LONG
                ).show();
              }

              @Override
              public void onError(String error) {
                Timber.e("On delete error: %s", error);
              }
            });
          }
        }

        @Override
        public void onError(String error) {
          Timber.e("onListError: %s", error);
        }
      });
    }
  }

  @Override
  public void onLowMemory() {
    super.onLowMemory();
    mapView.onLowMemory();
  }

  @Override
  protected void onDestroy() {
    super.onDestroy();
    mapView.onDestroy();
  }

  @Override
  protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    mapView.onSaveInstanceState(outState);
  }

  // Progress bar methods
  private void startProgress() {

    // Start and show the progress bar
    isEndNotified = false;
    progressBar.setIndeterminate(true);
    progressBar.setVisibility(View.VISIBLE);
  }

  private void setPercentage(final int percentage) {
    progressBar.setIndeterminate(false);
    progressBar.setProgress(percentage);
  }

  private void endProgress(final String message) {
    // Don't notify more than once
    if (isEndNotified) {
      return;
    }

    // Stop and hide the progress bar
    isEndNotified = true;
    progressBar.setIndeterminate(false);
    progressBar.setVisibility(View.GONE);

    // Show a toast
    Toast.makeText(SimpleOfflineMapActivity.this, message, Toast.LENGTH_LONG).show();
  }
}
```