Android (Jetpack Compose)
Android (Kotlin)
build.gradle.kts
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.compose.compiler)
alias(libs.plugins.play.services)
}
android {
namespace = "com.advantahealth.core.sample"
compileSdk = 36
defaultConfig {
applicationId = "com.advantahealth.core.sample"
minSdk = 26
targetSdk = 36
versionCode = 1
versionName = "1.0"
}
buildTypes {
getByName("debug") {
isDebuggable = true
}
getByName("release") {
isMinifyEnabled = false
}
}
buildFeatures {
compose = true
buildConfig = true
}
compileOptions {
isCoreLibraryDesugaringEnabled = true
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
lint {
warningsAsErrors = false
abortOnError = true
}
kotlin {
jvmToolchain(17)
}
}
dependencies {
implementation("com.advantahealth:core-android:1.1")
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.5")
debugImplementation("androidx.compose.ui:ui-tooling-preview:1.8.3")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2")
implementation("androidx.activity:activity-compose:1.10.1")
implementation("androidx.activity:activity-ktx:1.10.1")
implementation("androidx.appcompat:appcompat:1.7.1")
implementation("androidx.compose.ui:ui:1.8.3")
implementation("androidx.compose.ui:ui-tooling:1.8.3")
implementation("androidx.compose.foundation:foundation:1.8.3")
implementation("androidx.compose.material3:material3-android:1.3.2")
implementation("androidx.core:core-ktx:1.16.0")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.9.1")
implementation("com.google.android.material:material:1.12.0")
implementation("com.google.firebase:firebase-messaging:25.0.0")
}
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:usesCleartextTraffic="true"
>
<activity android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE" />
</intent-filter>
<!-- Permission handling for Android 14+ -->
<intent-filter>
<action android:name="android.intent.action.VIEW_PERMISSION_USAGE"/>
<category android:name="android.intent.category.HEALTH_PERMISSIONS"/>
</intent-filter>
</activity>
</application>
</manifest>
MainActivity.kt
class MainActivity : ComponentActivity() {
private val viewModel: MainViewModel by viewModels()
@OptIn(ExperimentalMaterial3Api::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
actionBar?.hide()
FirebaseApp.initializeApp(applicationContext)
AFPermissions.registerLifecycle(this@MainActivity)
setContent {
MaterialTheme {
Surface(modifier = Modifier.fillMaxSize()) {
AFCoreSampleApp(viewModel)
}
}
}
}
@Composable
fun AFCoreSampleApp(viewModel: MainViewModel) {
if (viewModel.isLoggedIn) {
MainSectionsScreen(this, viewModel)
} else {
LoginScreen(viewModel)
}
}
}
LoginScreen.kt
@Composable
fun LoginScreen(viewModel: MainViewModel) {
val context = LocalContext.current
val forgotPasswordUrl = "https://api.activefitplus.com/recoverpassword"
Column(
modifier = Modifier
.fillMaxSize()
.padding(24.dp),
verticalArrangement = Arrangement.Center
) {
Spacer(Modifier.weight(1f))
Text(
text = "Sign in to continue",
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
Spacer(Modifier.height(24.dp))
OutlinedTextField(
value = viewModel.username,
onValueChange = { viewModel.username = it },
label = { Text("Username") },
singleLine = true,
modifier = Modifier.fillMaxWidth()
)
Spacer(Modifier.height(16.dp))
OutlinedTextField(
value = viewModel.password,
onValueChange = { viewModel.password = it },
label = { Text("Password") },
singleLine = true,
visualTransformation = PasswordVisualTransformation(),
modifier = Modifier.fillMaxWidth()
)
Spacer(Modifier.height(8.dp))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.End
) {
TextButton(onClick = {
val intent = Intent(Intent.ACTION_VIEW, forgotPasswordUrl.toUri())
context.startActivity(intent)
}) {
Text(
text = "Forgot Password?",
style = MaterialTheme.typography.labelSmall
)
}
}
Spacer(Modifier.height(8.dp))
Button(
onClick = { viewModel.login() },
modifier = Modifier
.fillMaxWidth()
.height(48.dp)
) {
if (viewModel.isLoading) {
CircularProgressIndicator(
color = Color.White,
modifier = Modifier.size(20.dp),
strokeWidth = 2.dp
)
} else {
Text("Login")
}
}
Spacer(Modifier.weight(1f))
}
}
MainSectionsScreen.kt
@Composable
fun MainSectionsScreen(activity: ComponentActivity, viewModel: MainViewModel) {
val resultText by viewModel.resultTextFlow.collectAsState(initial = "")
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
if (resultText.isNotBlank()) {
Box(
modifier = Modifier
.fillMaxWidth()
.background(
color = MaterialTheme.colorScheme.surfaceVariant,
shape = RoundedCornerShape(8.dp)
)
.padding(12.dp)
) {
Text(
text = resultText,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
}
Spacer(modifier = Modifier.height(16.dp))
}
Text("🔐 Request Permissions", style = MaterialTheme.typography.titleMedium)
Button(onClick = { viewModel.requestPermission(Permission.FINE_LOCATION) }) {
Text("📍 Location (When In Use)")
}
Button(onClick = { viewModel.requestPermission(Permission.BACKGROUND_LOCATION) }) {
Text("📍 Location (Always)")
}
Button(onClick = { viewModel.requestPermission(Permission.BLUETOOTH_LE) }) {
Text("🔵 Bluetooth LE Access")
}
Button(onClick = { viewModel.requestPermission(Permission.PHYSICAL_ACTIVITY) }) {
Text("🏋️ Motion & Fitness Access")
}
HorizontalDivider(Modifier, DividerDefaults.Thickness, DividerDefaults.color)
Text("🔋 Health Connect Actions", style = MaterialTheme.typography.titleMedium)
Button(onClick = { viewModel.connectToHealthConnect(activity) }) {
Text("🔗 Connect to Health Connect")
}
HorizontalDivider(Modifier, DividerDefaults.Thickness, DividerDefaults.color)
Text("📍 Geofencing Actions", style = MaterialTheme.typography.titleMedium)
Button(onClick = { viewModel.startGeofencing() }) {
Text("🟢 Start Geofencing")
}
Button(onClick = {
viewModel.stopGeofencing()
}) {
Text("🔴 Stop Geofencing")
}
HorizontalDivider(Modifier, DividerDefaults.Thickness, DividerDefaults.color)
Text("📡 API Sample Calls", style = MaterialTheme.typography.titleMedium)
Button(onClick = { viewModel.fetchActivities() }) {
Text("👟 Fetch My Activities")
}
Button(onClick = { viewModel.fetchPayments() }) {
Text("💳 Fetch Payments History")
}
Button(onClick = { viewModel.fetchApplicationSettings() }) {
Text("⚙️ Load Application Settings")
}
Button(onClick = { viewModel.syncMessagingToken(activity) }) {
Text("🔥 Get Firebase Token")
}
Button(onClick = { viewModel.fetchMentalFitnessResources() }) {
Text("🧠 Fetch Mental Fitness Data")
}
Button(onClick = { viewModel.fetchMovementHealthStatus() }) {
Text("🏃♂️ Get Movement Health Status")
}
Button(onClick = { viewModel.fetchMyCarePathTasks() }) {
Text("📲 Get MyCarePath URL")
}
Button(onClick = { viewModel.fetchProfileInfo() }) {
Text("👤 Fetch Profile Info")
}
Button(onClick = { viewModel.fetchPrograms() }) {
Text("