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.ymlkeeps 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
SenderTransferDaoandReceiverTransferDaocontracts 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
minSdk28,targetSdk34,compileSdk34
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 testReleases
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.