Android Coden
Android 4 min lesen

Uploads in Android zuverlässig umsetzen

Uploads brauchen klare Zuständigkeiten, Wiederholungen und sichtbare Arbeit. So planst du robuste Dateiübertragungen in Android.

Ein Upload ist mehr als ein einzelner HTTP-Request. In einer echten Android-App muss er Netzprobleme, App-Wechsel, große Dateien, Akkuverbrauch und Nutzererwartungen berücksichtigen. Wenn du Fotos, Dokumente, Logs oder Formularanhänge sendest, brauchst du daher ein klares Modell: Die UI startet nicht einfach „irgendeinen Request“, sondern gibt eine Upload-Aufgabe an eine verlässliche Schicht ab.

Was ist das?

Uploads bedeuten, dass deine App Daten vom Gerät an ein Backend überträgt. Das können kleine JSON-Daten sein, häufig geht es aber um Dateien: Profilbilder, Rechnungen, Audioaufnahmen oder Diagnosepakete. Sobald Dateien beteiligt sind, nutzt du oft multipart/form-data. Dabei wird ein Request in mehrere Teile aufgeteilt, zum Beispiel ein Bildteil und ein Textteil mit Metadaten.

Im modernen Android-Kontext gehört Upload-Logik nicht direkt in einen Compose-Screen. Die UI sammelt Eingaben und zeigt Status an. Die eigentliche Arbeit liegt in der Data Layer, etwa in einem Repository, das mit einem API-Client und einer lokalen Datenquelle zusammenarbeitet. Das passt zur Architektur-Empfehlung: Datenoperationen sollen klar gekapselt sein, damit UI, Netzwerk und Persistenz nicht vermischt werden.

Wichtig ist auch der Offline-First-Gedanke. Ein Upload kann geplant werden, obwohl das Gerät gerade offline ist. Die App kann den Auftrag lokal speichern, später erneut versuchen und den Status anzeigen. Für Lernende ist das zentrale mentale Modell: Ein Upload ist oft ein Auftrag mit Zustand, nicht nur ein Funktionsaufruf.

Wie funktioniert es?

Technisch besteht ein robuster Upload meistens aus vier Teilen. Erstens erzeugst du die Nutzdaten, etwa eine Datei-Referenz und Metadaten. Zweitens übergibst du diese Daten an ein Repository. Drittens führt ein Netzwerk-Client den eigentlichen Multipart-Request aus. Viertens verwaltest du Status, Fehler und Wiederholungen.

Retry ist dabei kein pauschales „nochmal versuchen“. Du unterscheidest zwischen temporären Fehlern und dauerhaften Fehlern. Temporär sind zum Beispiel fehlendes Netz, Timeout oder ein Serverfehler mit Status 503. Dauerhaft sind etwa ungültige Dateitypen, fehlende Berechtigungen oder ein 400-Fehler, weil Pflichtdaten fehlen. Nur temporäre Fehler sollten automatisch wiederholt werden.

Für längere Uploads brauchst du außerdem korrektes Hintergrundverhalten. Android schränkt Hintergrundarbeit bewusst ein, damit Akku und System stabil bleiben. Wenn ein Upload für den Nutzer wichtig und länger sichtbar ist, etwa das Senden eines großen Videos, sollte er als user-visible Arbeit laufen. In der Praxis bedeutet das häufig WorkManager mit Foreground Work und einer Benachrichtigung. So sieht der Nutzer, dass die App aktiv Daten sendet, und das System behandelt die Arbeit passend.

In Compose beobachtest du dann nur den Zustand: wartend, läuft, erfolgreich, fehlgeschlagen. Die UI sollte nicht selbst entscheiden, wann ein Retry passiert. Diese Entscheidung gehört in die Schicht, die Netzwerk, lokale Queue und Fehlerregeln kennt.

In der Praxis

Ein einfaches Muster ist: Der Screen erstellt einen Upload-Auftrag, das Repository speichert ihn lokal, und ein Worker verarbeitet ihn. Der folgende Ausschnitt zeigt bewusst nur die Form der Verantwortung, nicht eine komplette Retrofit-Konfiguration.

class UploadWorker(
    context: Context,
    params: WorkerParameters,
    private val repository: UploadRepository
) : CoroutineWorker(context, params) {

    override suspend fun doWork(): Result {
        val uploadId = inputData.getString("upload_id") ?: return Result.failure()

        setForeground(createForegroundInfo(uploadId))

        return try {
            repository.uploadPendingItem(uploadId)
            Result.success()
        } catch (e: IOException) {
            Result.retry()
        } catch (e: InvalidUploadException) {
            Result.failure()
        }
    }

    private fun createForegroundInfo(uploadId: String): ForegroundInfo {
        val notification = NotificationCompat.Builder(applicationContext, "uploads")
            .setSmallIcon(R.drawable.ic_upload)
            .setContentTitle("Upload läuft")
            .setContentText("Datei wird übertragen")
            .setOngoing(true)
            .build()

        return ForegroundInfo(42, notification)
    }
}

Im Repository kann dann der Multipart-Teil liegen:

suspend fun uploadPendingItem(id: String) {
    val item = localDataSource.getUpload(id)
    val filePart = item.file.asRequestBody(item.mimeType.toMediaType())
    val multipart = MultipartBody.Part.createFormData(
        name = "file",
        filename = item.file.name,
        body = filePart
    )

    api.uploadFile(
        file = multipart,
        title = item.title.toRequestBody("text/plain".toMediaType())
    )

    localDataSource.markUploaded(id)
}

Die wichtigste Entscheidungsregel lautet: Alles, was nach App-Wechsel, Prozessende oder schlechtem Netz weiter relevant ist, darf nicht nur in einem ViewModel-Zustand leben. Speichere Upload-Aufträge lokal und verarbeite sie über eine geeignete Hintergrund-API. Ein ViewModel ist gut für UI-Zustand, aber kein verlässlicher Speicher für Netzwerkaufgaben.

Eine typische Stolperfalle ist ein endloser Retry bei falschen Daten. Wenn dein Server eine Datei ablehnt, weil sie zu groß ist, hilft ein neuer Versuch nicht. Dann muss die App den Auftrag als fehlgeschlagen markieren und dem Nutzer eine klare Korrektur ermöglichen. Eine zweite Stolperfalle ist fehlende Sichtbarkeit: Ein großer Upload ohne Benachrichtigung wirkt für Nutzer wie ein eingefrorener Vorgang und kann vom System ungünstig behandelt werden.

Achte im Code-Review besonders auf Zuständigkeiten. Compose sollte keine Multipart-Bodies bauen. Ein Repository sollte keine UI-Texte erzeugen. Der Worker sollte keine fachlichen Validierungen erraten, sondern klare Ergebnisse vom Repository bekommen. So bleibt der Upload testbar.

Fazit

Uploads werden stabil, wenn du sie als dauerhafte Aufgabe mit Status behandelst: lokal anlegen, über eine passende Data Layer ausführen, temporäre Fehler kontrolliert wiederholen und lange Arbeit sichtbar machen. Prüfe dein Verständnis praktisch, indem du einen Upload im Emulator bei deaktiviertem Netzwerk startest, danach das Netz wieder aktivierst und beobachtest, ob Status, Retry und Benachrichtigung korrekt funktionieren. Ergänze Tests für die Fehlerentscheidung: IOException darf retry liefern, ungültige Upload-Daten müssen sauber fehlschlagen.

Quellen (2)
Redaktion

Geschrieben von

Redaktion

Das Redaktionsteam recherchiert und schreibt Artikel zu aktuellen Themen rund um Tech, Lifestyle und Ratgeber.