SAF: Paper Datenbank auf dem externalStorage

  • Moin alle zusammen,


    Im Rahmen einer App Entwicklung/Erweiterung möchte ich gerne durch den Nutzer eingegebene Daten, die bisher im internalStorage landen, als Backup in den externalStorage schreiben, so dass diese dann ggf. auch wieder importiert werden können. Nun ist das Thema externalStorage und damit verbundene Lese-/Schreibrechte recht neu für mich und offenbar auch etwas komplexer, als ich dachte (erste Versuche waren jedenfalls nicht erfolgreich). Daher habe ich beschlossen, bei Null anzufangen und mich stückweise dem eigentlichen Ziel zu nähern - hoffentlich mit eurer lehrreichen Unterstützung. Zum Testen habe ich mir eine einfache GUI mit mehreren Buttons und Textzeilen erstellt, so dass ich auf Knopfdruck diverse Aktionen auslösen kann (später dann das Lesen und Speichern von Dateien).


    Der erste Schritt für mich ist, die Lese- und Schreibrechte abzufragen und zu setzen.


    Dazu das Manifest:


    Und hier der Java Code:


    Hier nun mein erster Stolperstein:


    Bei Erstinstallation der App (Android Emulator in Android Studio mit der API 30/Android 11) wird auf Anfrage auch brav die Permission abgefragt und gesetzt. Aber unabhängig davon, wie der User diese setzt, findet auf Anfrage keine erneute Abfrage statt! Es sei denn, ich ändere die Rechte per Hand über die Android-Einstellungen. Eine Option "nicht mehr fragen" erscheint erst gar nicht. Bei "requestPersmissions()" würde ich eigentlich stets den entsprechenden Abfragedialog von Android erwarten. Aktuell könnte ich den User nur auffordern, dies bei Bedarf manuell in den Einstellungen zu ändern, was aber etwas ineffizient anmutet ... habe ich hier einen Fehler im Code oder nur einen Denkfehler?

  • Hallo mal ein par grundlegende Sachen und Fragen.


    Du willst mit APi 30 arbeiten.

    In welchen Ordner Speiche Bereich willst du deine Daten ablegen. Das hatte ich schon im vorherigen Post gefragt. Keine Antwort darauf.

    Das ist aber wichtig essentiell. Denn in den APP eigen Bereichen brauchst du zb keine Permission. Auch nicht in den


    /storage/emulated/0/Android/data/deinPaketName/…. Bereich



    Und ab API 33 geht es auch nicht mehr mit den

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

    Da ist dann nur noch SAF angesagt.

  • Entschuldige meine fehlende Antwort. Die hatte ich ganz vergessen ...


    Grundsätzlich möchte ich die Daten gerne in einem öffentlichen Ordner wie z.B. Documents ablegen. Denn dort hat der User auch die Möglichkeit, exportierte Daten zum erneuten Import abzulegen. Der App eigene Bereich ist meines Wissens dem normalen User nicht zugänglich.


    Ich würde mich auch erstmal auf Versionen bis API 32 beschränken. Für höhere Versionen kann ich dann immer noch eine Fallunterscheidung wie für API 23 einfügen und entsprechend behandeln. Wäre jetzt zumindest mein erster Gedanke.

  • ... ich habe aber auch dann keine Abfrage mehr, wenn der Nutzer abgelehnt hat. Spätestens in dem Fall würde ich zumindest eine erneute Abfrage erwarten/wünschen.

    ich mache das so

    Code
    if (ContextCompat.checkSelfPermission(
                    this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
                ActivityCompat.requestPermissions(
                        this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 12345);
            } else {
                //dein code
            }
  • Habe mich mal gerade etwas in die Doku zu SAF vertieft. Offenbar ist diese schon ab API 19 verfügbar/nutzbar, so dass es vielleicht doch Sinn macht, darauf zurückzugreifen. Müsste dann wahrscheinlich nur die "minSDKVersion" im Manifest auf 19/21 festlegen, was aber OK ist.


    Ich bin grad soweit, dass ich über "ACTION_OPEN_DOCUMENT_TREE" und einem Intent Objekt eine URI erhalte. Bei der Auswahl des gewünschten Ordners werde ich auch wie erwartet nach meinen Rechten befragt. Soweit, so gut.


    Welche Möglichkeit habe ich nun, eine Pfadangabe zu meiner URI zu erhalten? Die Methode "getPath()" liefert mir "/tree/primary:Documents", was ich so aber nicht weiter verwenden kann ...


    Zur Erklärung: ich möchte die Daten gerne weiterhin als Paper Datenbank speichern (via Paper.bookOn), aber eben im ExternalStorage, brauche dafür aber auch eine entsprechende Pfadangabe.

  • clthaler

    Hat den Titel des Themas von „Lese-/Schreibrechte abfragen/setzen“ zu „SAF: Paper Datenbank auf dem externalStorage“ geändert.
  • ein sehr einfaches beispiel

  • Danke für das Beispiel. Wenn ich es teils mit "ACTION_OPEN_DOCUMENT_TREE" in meinem TestCode umsetze, erhalte ich jetzt auch die gewünschte Nachfrage nach Ort und Zugriffsberechtigung.


    Nun wird mir aber auch gesagt, dass "startActivityForResult" veraltet ist und durch einen "ActivityResultLauncher" ersetzt werden sollte. Nach ein paar Recherchen habe ich nun folgenden Code:


    Ich beschränke mich darauf, erstmal nur einen Text darzustellen, möchte dann aber letztendlich per "FileOutputStream" eine einzelne Textdatei schreiben.


    Nun ist es so, dass die App stoppt und das Logcat folgendes ausgibt:


    FATAL EXCEPTION: main

    Process: clthaler.appfortest, PID: 21927

    java.lang.IllegalStateException: Could not execute method for android:onClick

    at androidx.appcompat.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:473)

    at android.view.View.performClick(View.java:7448)

    at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:1131)

    at android.view.View.performClickInternal(View.java:7425)

    at android.view.View.access$3600(View.java:810)

    at android.view.View$PerformClick.run(View.java:28305)

    at android.os.Handler.handleCallback(Handler.java:938)

    at android.os.Handler.dispatchMessage(Handler.java:99)

    at android.os.Looper.loop(Looper.java:223)

    at android.app.ActivityThread.main(ActivityThread.java:7656)

    at java.lang.reflect.Method.invoke(Native Method)

    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)

    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)

    Caused by: java.lang.reflect.InvocationTargetException

    at java.lang.reflect.Method.invoke(Native Method)

    at androidx.appcompat.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:468)

    at android.view.View.performClick(View.java:7448)

    at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:1131)

    at android.view.View.performClickInternal(View.java:7425)

    at android.view.View.access$3600(View.java:810)

    at android.view.View$PerformClick.run(View.java:28305)

    at android.os.Handler.handleCallback(Handler.java:938)

    at android.os.Handler.dispatchMessage(Handler.java:99)

    at android.os.Looper.loop(Looper.java:223)

    at android.app.ActivityThread.main(ActivityThread.java:7656)

    at java.lang.reflect.Method.invoke(Native Method)

    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)

    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)

    Caused by: android.content.ActivityNotFoundException: No Activity found to handle Intent { act=android.intent.action.CREATE_DOCUMENT }

    at android.app.Instrumentation.checkStartActivityResult(Instrumentation.java:2067)

    at android.app.Instrumentation.execStartActivity(Instrumentation.java:1727)

    at android.app.Activity.startActivityForResult(Activity.java:5314)

    at androidx.activity.ComponentActivity.startActivityForResult(ComponentActivity.java:728)

    at androidx.core.app.ActivityCompat$Api16Impl.startActivityForResult(ActivityCompat.java:809)

    at androidx.core.app.ActivityCompat.startActivityForResult(ActivityCompat.java:246)

    at androidx.activity.ComponentActivity$2.onLaunch(ComponentActivity.java:243)

    at androidx.activity.result.ActivityResultRegistry$2.launch(ActivityResultRegistry.java:175)

    at androidx.activity.result.ActivityResultLauncher.launch(ActivityResultLauncher.java:47)

    at clthaler.appfortest.MainActivity.button1(MainActivity.java:52)

    at java.lang.reflect.Method.invoke(Native Method)

    at androidx.appcompat.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:468)

    at android.view.View.performClick(View.java:7448)

    at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:1131)

    at android.view.View.performClickInternal(View.java:7425)

    at android.view.View.access$3600(View.java:810)

    at android.view.View$PerformClick.run(View.java:28305)

    at android.os.Handler.handleCallback(Handler.java:938)

    at android.os.Handler.dispatchMessage(Handler.java:99)

    at android.os.Looper.loop(Looper.java:223)

    at android.app.ActivityThread.main(ActivityThread.java:7656)

    at java.lang.reflect.Method.invoke(Native Method)

    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)

    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)


    Ich erhalte auch keine Abfrage des Ortes und der notwendigen Berechtigung, wie es bei "ACTION_OPEN_DOCUMENT_TREE" der Fall war. Und wenn ich es richtig verstehe, erhält der Launcher aufgrund dessen keine Rückgabe, die von ihm verarbeitet werden kann, was dann zu diesem Fehler führt. Die Ausnahme kann ich natürlich abfangen, aber das löst natürlich nicht das Problem.


    Gleiches passiert, wenn ich "ACTION_OPEN_DOCUMENT" verwende. Irgendwo ist im Code oder in meinem Verständnis ist also noch der Wurm drin.

  • Hallo

    die Fehlermeldung sagt es dir doch schon ziemlich genau.

    Du kannst bei ViewBindig das Onclick Tag im Xml nicht mehr nutzen.

    Das geht ja auch schon seit Fragments nicht mehr.


    Du musst einen Listener im Code auf dem Button setzen.

    Da zu kannst du seit Java 8 auch Lambdas benutzen.


    Du scheinst noch einen sehr alten Programmierstiel zu verfolgen.

  • die Fehlermeldung sagt es dir doch schon ziemlich genau.

    Du kannst bei ViewBindig das Onclick Tag im Xml nicht mehr nutzen.

    Das geht ja auch schon seit Fragments nicht mehr.

    ... schade, dabei fand ich das OnClick Tag bisher ganz praktisch im Vergleich zum Listener. Aber ich bin ja flexibel.


    Du scheinst noch einen sehr alten Programmierstiel zu verfolgen.


    ... ist eher die Bequemlichkeit, eine noch funktionierende Programmierung nicht zu ändern. Wenn etwas Neues für mich dazukommt, tu ich mich meist etwas schwer damit, es zu verstehen. Aber dafür gibt es ja Profis, denen ich hoffentlich auch mal "dumme" Fragen stellen darf ;)


    Danke dafür ...

Jetzt mitmachen!

Sie haben noch kein Benutzerkonto auf unserer Seite? Registrieren Sie sich kostenlos und nehmen Sie an unserer Community teil!