Android SDK (Kotlin)
Native Android SDK built with Kotlin for optimal performance and native Android integration.
Status
🔄 Coming Q3 2025 - Currently in development
Join the waitlist to be notified when available.
Planned Features
Core Features
- ✅ Wallet-to-wallet calls (audio/video)
- ✅ Native ConnectionService integration
- ✅ Push notifications (FCM)
- ✅ Messaging
- ✅ Voicemail
- ✅ Call history
Android-Specific
- ✅ ConnectionService integration
- ✅ Notification channels
- ✅ Picture-in-Picture mode
- ✅ Android Auto support
- ✅ Widgets
- ✅ Material Design 3 components
- ✅ Jetpack Compose support
Requirements
- Android 8.0+ (API 26+)
- Kotlin 1.9+
- Android Studio Hedgehog or later
Planned Installation
Gradle (Kotlin DSL)
dependencies {
implementation("wtf.dial:sdk-android:1.0.0")
}Gradle (Groovy)
dependencies {
implementation 'wtf.dial:sdk-android:1.0.0'
}Planned API
Setup
import wtf.dial.sdk.DialClient
val dial = DialClient(
context = applicationContext,
apiKey = "your-api-key",
walletAddress = "0x..."
)Authentication
// Sign message with wallet
val signature = wallet.signMessage("Authenticate with Dial")
dial.authenticate(
walletAddress = "0x...",
signature = signature
)Making Calls
val call = dial.calls.start(
to = "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
type = CallType.VIDEO
)ConnectionService Integration
import android.telecom.ConnectionService
import wtf.dial.sdk.DialConnectionService
class MyConnectionService : DialConnectionService() {
override fun onCreateIncomingConnection(
connectionManagerAccount: PhoneAccountHandle,
request: ConnectionRequest
): Connection {
// Automatically integrates with Android's native call UI
return createDialConnection(request)
}
}Add to AndroidManifest.xml:
<service
android:name=".MyConnectionService"
android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE">
<intent-filter>
<action android:name="android.telecom.ConnectionService" />
</intent-filter>
</service>Jetpack Compose Integration
import androidx.compose.runtime.*
import wtf.dial.sdk.compose.*
@Composable
fun CallScreen() {
val callState = rememberDialCallState()
val dial = LocalDial.current
Column {
if (callState.currentCall != null) {
when (callState.currentCall.status) {
CallStatus.RINGING -> IncomingCallView(callState.currentCall)
CallStatus.ACTIVE -> ActiveCallView(callState.currentCall)
else -> {}
}
} else {
Button(onClick = {
callState.startCall(
to = "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
type = CallType.VIDEO
)
}) {
Text("Make Call")
}
}
}
}
@Composable
fun IncomingCallView(call: DialCall) {
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
text = "Incoming call from",
style = MaterialTheme.typography.bodyLarge
)
Text(
text = call.from,
style = MaterialTheme.typography.headlineMedium
)
Row(
modifier = Modifier.padding(top = 32.dp),
horizontalArrangement = Arrangement.spacedBy(32.dp)
) {
FloatingActionButton(
onClick = { call.decline() },
containerColor = MaterialTheme.colorScheme.error
) {
Icon(Icons.Default.CallEnd, "Decline")
}
FloatingActionButton(
onClick = { call.answer() },
containerColor = MaterialTheme.colorScheme.primary
) {
Icon(Icons.Default.Call, "Answer")
}
}
}
}Traditional View System
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import wtf.dial.sdk.DialClient
import wtf.dial.sdk.DialCallListener
class CallActivity : AppCompatActivity(), DialCallListener {
private lateinit var dial: DialClient
private var currentCall: DialCall? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_call)
dial = DialClient.getInstance()
dial.addCallListener(this)
}
fun makeCall(recipient: String) {
lifecycleScope.launch {
currentCall = dial.calls.start(
to = recipient,
type = CallType.VIDEO
)
}
}
override fun onIncomingCall(call: DialCall) {
currentCall = call
showIncomingCallUI(call)
}
}Messaging
// Send message
dial.messages.send(
to = "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
content = "Hello!",
type = MessageType.TEXT
)
// Listen for messages
dial.messages.onReceived { message ->
println("New message from ${message.from}: ${message.content}")
}Push Notifications (FCM)
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import wtf.dial.sdk.DialNotificationHandler
class MyFirebaseMessagingService : FirebaseMessagingService() {
override fun onMessageReceived(remoteMessage: RemoteMessage) {
DialNotificationHandler.handleNotification(
context = this,
data = remoteMessage.data
)
}
override fun onNewToken(token: String) {
DialClient.getInstance().registerPushToken(token)
}
}Coroutines Support
Full support for Kotlin Coroutines:
// All API calls are suspend functions
lifecycleScope.launch {
val call = dial.calls.start(to = "0x...", type = CallType.AUDIO)
val messages = dial.messages.getHistory(with = "0x...")
val voicemails = dial.voicemail.getAll()
}Flow Support
Reactive streams with Kotlin Flow:
import kotlinx.coroutines.flow.*
class CallViewModel : ViewModel() {
val currentCall: StateFlow<DialCall?> =
dial.callFlow.stateIn(
viewModelScope,
SharingStarted.Lazily,
null
)
val messages: StateFlow<List<DialMessage>> =
dial.messagesFlow.stateIn(
viewModelScope,
SharingStarted.Lazily,
emptyList()
)
}Android Auto Integration
import androidx.car.app.CarContext
import androidx.car.app.Screen
import wtf.dial.sdk.auto.DialAutoScreen
class DialAutoCallScreen(carContext: CarContext) : DialAutoScreen(carContext) {
override fun onGetTemplate(): Template {
return createCallTemplate()
}
}Add to AndroidManifest.xml:
<meta-data
android:name="com.google.android.gms.car.application"
android:resource="@xml/automotive_app_desc" />Widgets
import android.appwidget.AppWidgetProvider
import wtf.dial.sdk.widget.CallHistoryWidget
class DialWidget : CallHistoryWidget() {
override fun onUpdate(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetIds: IntArray
) {
// Automatically displays recent calls
super.onUpdate(context, appWidgetManager, appWidgetIds)
}
}Picture-in-Picture
import android.app.PictureInPictureParams
import android.util.Rational
fun enterPipMode() {
val params = PictureInPictureParams.Builder()
.setAspectRatio(Rational(16, 9))
.build()
enterPictureInPictureMode(params)
}
override fun onPictureInPictureModeChanged(
isInPictureInPictureMode: Boolean,
newConfig: Configuration
) {
if (isInPictureInPictureMode) {
// Hide UI elements
hideCallControls()
} else {
// Show UI elements
showCallControls()
}
}Permissions
Add to AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<!-- For ConnectionService -->
<uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />Runtime permission handling:
val permissions = arrayOf(
Manifest.permission.CAMERA,
Manifest.permission.RECORD_AUDIO
)
ActivityCompat.requestPermissions(this, permissions, REQUEST_CODE)ProGuard Rules
Add to proguard-rules.pro:
-keep class wtf.dial.sdk.** { *; }
-keepclassmembers class wtf.dial.sdk.** { *; }Performance
- Native Kotlin implementation
- Hardware-accelerated codecs
- Battery optimization
- Background execution limits handling
- Low memory footprint
Material Design 3
Full Material You support:
@Composable
fun DialTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
dynamicColor: Boolean = true,
content: @Composable () -> Unit
) {
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
if (darkTheme) dynamicDarkColorScheme(LocalContext.current)
else dynamicLightColorScheme(LocalContext.current)
}
darkTheme -> darkColorScheme()
else -> lightColorScheme()
}
MaterialTheme(
colorScheme = colorScheme,
content = content
)
}Roadmap
Q3 2025 - Alpha Release
- Core calling features
- ConnectionService integration
- Push notifications
- Messaging
Q4 2025 - Beta Release
- Video conferencing
- Android Auto support
- Widgets
- Picture-in-Picture
Q1 2026 - Stable Release
- Full feature parity
- Material Design 3
- Production-ready
- Complete documentation
Example App
A complete example app will be available:
- GitHub Repository
- Jetpack Compose implementation
- Traditional Views implementation
- Best practices
- Material Design 3 patterns
Get Notified
Want early access?
Next Steps
- React Native SDK - Coming Q2 2025
- iOS SDK - Coming Q3 2025
- TypeScript SDK - Available now
Last updated on