GitHub - BlueCodeSystems/android-p2p-sync

JitPack Latest Build for latest tag (v0.5.0) master-SNAPSHOT

Android library for peer-to-peer record synchronization in the OpenSRP ecosystem, built on Google Nearby Connections so devices can exchange data even without conventional network access.

Project Status

  • Toolchain: Gradle 8.7, Android Gradle Plugin 8.5.2, JDK 17; Kotlin is not required by the library.
  • CI: Gradle release build executed through jitpack.yml keeps publishing tasks green.
  • Default branch: master; latest tag: v0.5.0.

Features

  • Offline-first synchronization channel that transfers JSON and multimedia payloads over Nearby Connections.
  • Pluggable SenderTransferDao and ReceiverTransferDao contracts that adapt to any local data store.
  • Built-in activities and fragments that guide users through send/receive flows with authorization hooks.
  • Optional callbacks for progress reporting and post-transfer handling.
  • Sample app demonstrating end-to-end setup with mock data providers.

Requirements

  • JDK 17
  • Gradle 8.7 (managed through the included wrapper)
  • Android Gradle Plugin 8.5.2
  • AndroidX / Jetifier enabled
  • minSdk 28, targetSdk 34, compileSdk 34

Install

Add Maven Central and depend on the artifact that matches your release. Replace <version> with the version listed on the Releases page.

dependencyResolutionManagement {
    repositories {
        mavenCentral()
    }
}

dependencies {
    implementation 'io.github.bluecodesystems:android-p2p-sync:<version>'
}
repositories {
    mavenCentral()
}

dependencies {
    implementation("io.github.bluecodesystems:android-p2p-sync:<version>")
}

Initialize

Call the library once from your Application class to provide context, credentials, and data access objects.

public final class MyApp extends Application {
    @Override
    public void onCreate() {
        super.onCreate();

        P2PLibrary.Options options = new P2PLibrary.Options(
                this,
                "encrypted_db_passphrase",
                "John Doe",
                new MyAuthorizationService(),
                new MyReceiverDao(),
                new MySenderDao()
        );
        options.setBatchSize(200);
        options.setSyncFinishedCallback(new SyncFinishedCallback() {
            @Override
            public void onSuccess(@NonNull HashMap<String, Integer> transferRecords) {
                // Update UI or analytics when records arrive.
            }

            @Override
            public void onFailure(@NonNull Exception exception,
                                   @Nullable HashMap<String, Integer> transferRecords) {
                // Handle retry strategy or user messaging.
            }
        });

        P2PLibrary.init(options);
    }
}

Usage examples

Provide transfer contracts

Implement ReceiverTransferDao to accept incoming payloads and acknowledge the last processed record. Multimedia files arrive on a worker thread, so you can persist them immediately.

public final class MyReceiverDao implements ReceiverTransferDao {
    private final Map<String, Long> lastReceived = new HashMap<>();

    @Override
    public TreeSet<DataType> getDataTypes() {
        TreeSet<DataType> dataTypes = new TreeSet<>();
        dataTypes.add(new DataType("names", DataType.Type.NON_MEDIA, 0));
        dataTypes.add(new DataType("profile_photos", DataType.Type.MEDIA, 1));
        return dataTypes;
    }

    @Override
    public long receiveJson(@NonNull DataType type, @NonNull JSONArray payload) {
        long lastId = lastReceived.getOrDefault(type.getName(), 0L) + payload.length();
        lastReceived.put(type.getName(), lastId);
        return lastId;
    }

    @Override
    public long receiveMultimedia(@NonNull DataType dataType, @NonNull File file,
                                  @Nullable HashMap<String, Object> details, long recordId) {
        // Persist file and return the associated record id when successful.
        return recordId;
    }
}

Pair it with SenderTransferDao to expose batches of outbound records. The library requests JSON first and then multimedia when available.

public final class MySenderDao implements SenderTransferDao {
    @Override
    public TreeSet<DataType> getDataTypes() {
        TreeSet<DataType> dataTypes = new TreeSet<>();
        dataTypes.add(new DataType("names", DataType.Type.NON_MEDIA, 0));
        return dataTypes;
    }

    @Override
    public JsonData getJsonData(@NonNull DataType type, long lastRecordId, int batchSize) {
        JSONArray records = fetchNamesAfter(lastRecordId, batchSize);
        long highestId = records.length() > 0 ? lastRecordId + records.length() : lastRecordId;
        return new JsonData(records, highestId);
    }

    @Override
    public MultiMediaData getMultiMediaData(@NonNull DataType type, long lastRecordId) {
        return null; // Provide when you have files to share.
    }
}

Helper methods such as fetchNamesAfter illustrate app-specific data access.

Authorize peers

Supply a P2PAuthorizationService to guard who may connect and exchange data.

public final class MyAuthorizationService implements P2PAuthorizationService {
    @Override
    public void getAuthorizationDetails(@NonNull OnAuthorizationDetailsProvidedCallback callback) {
        Map<String, Object> details = new HashMap<>();
        details.put(Constants.AuthorizationKeys.PEER_STATUS, Constants.PeerStatus.SENDER);
        callback.onAuthorizationDetailsProvided(details);
    }

    @Override
    public void authorizeConnection(@NonNull Map<String, Object> details,
                                    @NonNull AuthorizationCallback callback) {
        if (userHasSyncRole()) {
            callback.onConnectionAuthorized();
        } else {
            callback.onConnectionAuthorizationRejected("User lacks sync permissions.");
        }
    }
}

Launch the peer-to-peer UI

Start P2pModeSelectActivity when you want the user to choose between sending and receiving. Override the processing_disclaimer string resource in your app if you need custom guidance during long-running imports.

startActivity(new Intent(currentActivity, P2pModeSelectActivity.class));

Sample app

A runnable sample module lives under sample/. Install it on a connected device or emulator with:

./gradlew :sample:installDebug

You can also open the project in Android Studio and run the sample configuration directly.

Build & test

./gradlew clean assemble
./gradlew test

Releases

See the GitHub Releases for published versions, change notes, and migration guidance.

Contributing

Issues and pull requests are welcome. Please build and test with JDK 17, Gradle 8.7, and AGP 8.5.2 to match the current toolchain. If you introduce new examples or guidance, keep the README in sync.

License

Licensed under the Apache License 2.0.