AE Android Kochbuch: Notizen App. Dateien speichern

Im Teil3 vom AE Android Kochbuch schreiben wir eine kleine Notizen App und speichern diese als Datei.

Unser AE Android Kochbuch für Praktiker. Hier zeige ich euch wie ihr schnell und einfach Android programmieren lernt. Tipps aus der Praxis. Vorweg noch mal der Hinweis: Dieses ist zwar ein Grundkurs, aber ihr solltet schon etwas Grundlagenwissen haben. Zum Beispiel wissen, was eine Variable ist, und dass man Zahlen in Integer und Texte in String Variablen speichert. Wenn euch solche Basics noch fremd sind – ich empfehle die ersten Kapitel vom Buch Java ist auch nur eine Insel oder andere Literatur, die euch in solche grundlegenden Dinge einführt.


Hinweis: Für diesen Teil gibt es einen Nachfolger:

AE Android Kochbuch Teil 3A: Notizen App Reloaded


Die Idee der Notizbuch App

Wir wollen mit dem Android Studio eine kleine App schreiben mit der man Notizen schreiben und speichern kann. Was mich an der Android Notizen im System stört: ich muss mittels Google Konto eingelegt sein, um sie zu verwenden. Das soll bei unserer App nicht sein. Einfach schön und schlank: App starten – Text schreiben – speichern. Und vielleicht noch das eine oder andere Feature mehr.

Basis unserer App sind Sachen, die wir bereits in Teil1 und Teil2 besprochen haben. Hier haben wir Grundlagen gelegt, um um mit unserer Android App Texte anzuzeigen, Eingaben zu tätigen und Buttons zu klicken. Weiteres folgt dann im Lauf dieses Teiles, u.a. basteln wir nun eigene Methoden, um Daten zu speichern.

Die anderen Teile im AE Android Kochbuch gibt es hier.

Neue App anlegen: Writer

Wir starten das Studio, wählen neues Projekt und legen ein neues Projekt Writer oder AE Writer an. Den Titel können ihr euch selbst überlegen. Wie das mit dem Projekt anlegen funktioniert, wurde in Teil1 Getting Started erklärt!

Unser Layout

Zuerst einmal brauchen wir in unserer Layout Datei drei Elemente:

1) Eine TextView als Überschrift
2) Ein EditText Element, das sich über mehrere Zeilen erstreckt und eine Eingabe erlaubt
3) Einen Button, der unsere Eingabe löscht.

<?xml version="1.0" encoding="utf-8"?>
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
xmlns:app="http://schemas.android.com/apk/res-auto" 
xmlns:tools="http://schemas.android.com/tools" 
android:layout_width="match_parent" 
android:layout_height="match_parent" 
android:orientation="vertical" 
tools:context=".MainActivity"> 

<TextView 
android:layout_width="wrap_content" 
android:layout_height="wrap_content" 
android:text="Texteingabe" /> 

<EditText 
android:id="@+id/editTextText" 
android:layout_width="match_parent" 
android:layout_height="300dp" 
android:background="@drawable/drawback" 
android:gravity="top" 
android:scrollHorizontally="false" 
android:scrollbars="vertical" 
android:inputType="textMultiLine" 
android:text="" /> 

<Button 
android:id="@+id/buttonClear" 
android:layout_width="120dp" 
android:layout_height="wrap_content" 
android:text="Clear" /> 

</LinearLayout>

Das Ganze sieht in der Displayvorschau dann so aus:

Der Java Programm Code

Mit den Erkenntnissen aus den ersten beiden Teilen wisst ihr schon, was im Java Code passieren muss. Wir brauchen eine globale private static Variable, wenn wir den Eingabetext aus EditText in verschiedenen Methoden der Klasse auswerten oder verändern wollen. Also ganz oben am Anfangsbereich unserer Klasse eintragen:

private static EditText etEingabe1;

In der Methode onCreate müssen wir die EditText Variable initialisieren, damit wir später die Eingaben lesen oder verändern können.

 etEingabe1 = findViewById(R.id.editTextText);

Dann wollen wir noch einen onClick Listener für den Button Clear registrieren.

findViewById(R.id.buttonClear).setOnClickListener(this);

Wie immer, wenn wir erstmalig einen onClick Listener zufügen, werden vom Studio eine Reihe von Sachen erledigt, die mehr oder minder automatisch ablaufen. Wenn Ihr dazu mehr wissen wollt – siehe unseren Teil2 dieses Kochbuchs!

Mit der Methode onClick haben wir dann den Ort erhalten, in dem wir festlegen, was passieren soll, wenn der Benutzer den Button Clear betätigt. In unserem Fall wollen wir EditText löschen und anschließend den Cursor darauf setzen, damit eine neue Eingabe getätigt werden kann.

@Override 
public void onClick(View v) { 
   //Unser Eingabe Listener
   switch (v.getId()) { 
      case R.id.buttonClear: 
      //Button CLEAR
      etEingabe1.setText(""); 
      etEingabe1.requestFocus(); 
      break; 
   } 
}

Umfassend sieht der Java Code dann wie folgt aus:

package de.terminalsystems.aewriter; 

import ... 

//----------------------------------------------------------------------
 public class MainActivity extends AppCompatActivity implements View.OnClickListener { 

private static EditText etEingabe1; 

//----------------------------------------------------------------------
@Override 
protected void onCreate(Bundle savedInstanceState) { 
   super.onCreate(savedInstanceState); 
   setContentView(R.layout.activity_main); 

   etEingabe1 = findViewById(R.id.editTextText); 

   findViewById(R.id.buttonClear).setOnClickListener(this); 

   //am Start: Cursor in das Textfeld setzen
   etEingabe1.requestFocus(); 
}


//----------------------------------------------------------------------
 @Override 
public void onClick(View v) { 
   //Unser Eingabe Listener

   switch (v.getId()) { 
      case R.id.buttonClear: 
      //Button CLEAR
      etEingabe1.setText(""); 
      etEingabe1.requestFocus(); 
      break; 
   } 
} //end class

Erfolgserlebnis: Anwendung im Device Manager ausführen

Wenn wir die Anwendung jetzt kurz im Device Manager auf einem simulierten Smartphone ausprobieren, macht es schon einen halbwegs fertigen Eindruck! Alle Elemente erscheinen, die Zusatztastatur für Texteingabe wird eingeblendet und wir können einen Text tippen und der Clear Button löscht den auch.

Eingabe speichern

Wer schon Erfahrungen mit anderen Programmiersprachen hat, kennt die Abläufe beim Daten speichern. Datei mittels Writer öffnen. Daten hinein schreiben. Datei / Writer schließen. Fertig! Schon ist eine Datei erstellt, die man an anderer Stelle auswerten kann, z.B. an einen Computer übertragen, ausdrucken oder sonst wie bearbeiten. Nicht anders läuft es auch bei Android.

Doch zuerst bauen wir den Button Speichern mit der ID buttonSpeichern in die Layout Datei. Hier integrieren wir auch erstmals ein horizontales Layout in unser vertikales hinein, damit die Buttons nebeneinander stehen.

<TextView 
android:layout_width="wrap_content" 
android:layout_height="wrap_content" 
android:text="Texteingabe" /> 

<EditText 
android:id="@+id/editTextText" 
android:layout_width="match_parent" 
android:layout_height="300dp" 
android:background="@drawable/drawback" 
android:gravity="top" 
android:scrollHorizontally="false" 
android:scrollbars="vertical" 
android:inputType="textMultiLine" 
android:text="" />

Verändert wird ab hier:


<LinearLayout 
android:layout_width="match_parent" 
android:layout_height="wrap_content" 
android:orientation="horizontal"> 

<Button 
android:id="@+id/buttonClear" 
android:layout_width="120dp" 
android:layout_height="wrap_content" 
android:text="Clear" /> 

<Button 
android:id="@+id/buttonSpeichern" 
android:layout_width="120dp" 
android:layout_height="wrap_content" 
android:layout_marginLeft="10dp" 
android:text="Speichern" /> 

</LinearLayout>

Damit zwischen den Buttons ein kleiner Abstand ist, haben wir beim buttonSpeichern erstmals einen Rand nach links eingeführt. So wird der neue Button etwas vom bereits bestehenden Button abgesetzt. Die Buttons kleben nicht so aneinander.

In onCreate müssen wir den button Speichern für den onClick Listener registrieren.

findViewById(R.id.buttonSpeichern).setOnClickListener(this);

In der onClick Methode fügen wir schon mal die Behandlung für den neuen Button ein.

Den Code reichen wir später nach.

case R.id.buttonSpeichern: 
   //hier kommt das Speichern rein!
   break;

Kommen wir zum Programmcode für das Speichern! Hier basteln wir uns eine eigene, neue Methode zum Speichern der Daten. Wenn wir später mal anders speichern wollen, können wir diese Methode dann schnell austauschen.

Methode zum Speichern der Daten deklarieren

Irgendwo in eurer Klasse aber außerhalb der bestehenden Methoden könnt ihr jetzt eure erste eigene Methode einbauen:

private boolean doEingabeSpeichern (String myData) { 
   //Hier speichern wir Daten
   //Return: true wenn erfolgreich, false wenn Fehler
   return false; 
}

Unsere erste eigene Methode bekommt eine Angabe private zu Lebensdauer. Da wir sie nur in dieser Klasse verwenden ist sie private. Sie soll uns einen Boolean Wert zurückliefern ob das Speichern geklappt hat (true) oder ob es Fehler gegeben hat (false). Die Daten zum Speichern übergeben wir als String Wert beim Aufruf der Methode.

Auch wenn die Methode noch nichts macht, damit die Syntaxprüfung die neue Methode akzeptiert, tragen wir prophylaktisch schon mal return false ein. Würden wir diese Zeile weglassen gibt es Mecker, weil eine als boolean definierte Methode IMMER einen Returnwert true oder false zurückliefern muss.

Eigentlich könnten wir sie jetzt mit Leben füllen. Doch wie es so spielt – manche Methoden erfordert mehr Informationen – und so springe ich gleich zum vollständigen Abbild der Speichern Methode. Erklärung folgt im Anschluss.

In eine Datei speichern

Wir füllen nun die Methode mit Leben, die das speichern von Daten übernimmt. Die Daten werden beim Aufruf der Methode als Parameter übergeben. Genauso wie der Dateiname. Dazu kommt noch ein Context mit dem Android intern unterschiedliche Projekte identifiziert.

private boolean doEingabeSpeichern (Context context, String myFileName, String myData) { 
   //Hier speichern wir Daten
   //Return: true wenn erfolgreich, false wenn Fehler

   File myFile = new File (context.getFilesDir(), myFileName); 

   //Entscheidg ob Datwei existiert. Fuer write Kopfdaten
   boolean flagnewfile = (myFile.exists()) ? false : true; 

   FileOutputStream outputStream = null; 
   OutputStreamWriter ofw;

Wir verwenden für unseren Speichervorgang den File-Konstruktor File, der uns eine Datei im von Android benutzten Dateisystem anlegt. Dann deklarieren wir uns einen Output Stream und einen Streamwriter mit dem das Speichern im File erledigt werden soll.

Davor gibt es noch ein kleines Schmankerl: mit dem boolean Flag flagnewfile merken wir uns, ob die Datei schon existiert oder erstmalig erzeugt wird. Dieses Flag können wir später verwenden wenn wir zum Beispiel am Anfang der Datei Kopfdaten speichern möchten. Wenn euch der Sinn und Unsinn von Flags noch nicht ganz klar ist – wir haben einen kleinen Exkurs: AE Android Kochbuch. Flags. Spaß mit Flaggen.

https://www.art-events.de/weblog/ae-android-kochbuch-exkurs-flags-die-sachen-mit-den-flaggen/

Speichern mit try / catch

Unsere Speicherfunktion setzt sich fort:

try { 
   myFile.createNewFile(); // if file already exists will do nothing
   Log.d ("Hier gespeichert:", myFile.toString()); 
   outputStream = new FileOutputStream(myFile, true); 
   ofw = new OutputStreamWriter(outputStream, StandardCharsets.ISO_8859_1); 
 
   if (flagnewfile) { 
      //Falls Datei bisher nicht existierte koennten wir hier Kopfzeile schreiben!
      Log.d ("EingabeSpeichern", "Kopf anlegen"); 
      flagnewfile = false; 
   } 

   //wir speichern daten und erweitern Zeile um CRLF
   ofw.write(myData + "\r\n"); 
   //dann speichern wir noch trenner zwischen den texten
   ofw.write("------------------------------\r\n"); 
   Log.d ("Daten schreiben", myData); 
   ofw.close(); 

   } catch (Exception e) { 
   String x2 = e.getMessage(); 
   Log.e("Error0149", x2); 
   e.printStackTrace(); 
   return false; 
}

return true;

 

Im zweiten Teil der Methode folgt der eigentliche Speichervorgang. Diesen kapseln wir in einer try – catch Schleife, für den Fall, dass Android Fehler meldet. Bei einer solchen Fehlermeldung wird die Verarbeitung im try Zweig unterbrochen, wenn das Programm eigentlich abstürzen würde – und das Programm setzt im catch Zweig fort. Im catch Exception Zweig lassen wir uns die Fehlermeldung von Android als Debug Meldung ausgeben und beenden die Speichermethode dann mit false, so dass wir im Hauptprogramm auch eine Meldung an den Benutzer geben können, dass sein Speicherversuch nicht geklappt hat.

Im Hauptzweig von try erzeugen wir unsere Datei, falls sie noch nicht existiert und aktiveren unseren Streamwriter. Dabei setzen wir auch gleich ein Charakter Set, um z.B. Umlaute zu verarbeiten, die der Benutzer eingegeben hat. Die verschiedenen ISO Charakter Sets bieten euch unterschiedliche Möglichkeiten zum Speichern regionaler Zeichen.

Dann werten wir auch das boolean Flag flagnewfile aus. Sollte die Datei bisher nicht existieren, können wir hier eine Kopfzeile speichern. Ob das hier Sinn oder Unsinn macht, sei dahingestellt. Aber manchmal kommt es halt vor, dass man am Anfang einer Datei Kopfzeilen speichern will, z.b. wenn ihr XLS CSV Dateien für Excel aufbauen möchtet.

Die nachfolgenden Zeilen mit ofw.write sind es schlussendlich, die unsere Daten als String in eine Datei speichern. Jeder String wird dabei noch um einen Text Zeilenvorschlag CRLF erweitert und nach dem Speichern des Datenstrings fügen wir noch eine Trennzeile mit vielen Strichen an.

Die gesamte Speicherroutine in voller Pracht stellt sich dann so da:

//----------------------------------------------------------------------
 private boolean doEingabeSpeichern (Context context, String myFileName, String myData) { 
   //Hier speichern wir Daten
   //Return: true wenn erfolgreich, false wenn Fehler

   File myFile = new File (context.getFilesDir(), myFileName); 

   //Entscheidg ob Datwei existiert. Fuer write Kopfdaten
   boolean flagnewfile = (myFile.exists()) ? false : true; 

   FileOutputStream outputStream = null; 
   OutputStreamWriter ofw; 
   try { 
      myFile.createNewFile(); // if file already exists will do nothing
      Log.d ("Hier gespeichert:", myFile.toString()); 
      outputStream = new FileOutputStream(myFile, true); 
      ofw = new OutputStreamWriter(outputStream, StandardCharsets.ISO_8859_1); 

      if (flagnewfile) { 
         //Falls Datei bisher nicht existierte koennten wir hier Kopfzeile schreiben!
         Log.d ("EingabeSpeichern", "Kopf anlegen"); 
         flagnewfile = false; 
      } 

      //wir speichern daten und erweitern Zeile um CRLF
      ofw.write(myData + "\r\n"); 
      //dann speichern wir noch trenner zwischen den texten
      ofw.write("------------------------------\r\n"); 
      Log.d ("Daten schreiben", myData); 
      ofw.close(); 

   } catch (Exception e) { 
      String x2 = e.getMessage(); 
      Log.e("Error0149", x2); 
      e.printStackTrace(); 
      return false; 
   } 
   return true; 
}

KeyListener onClick erweitern

Bleibt nur noch eine Sache: wir müssen den Aufruf der Speichermethode im Key Listener unterbringen.

case R.id.buttonSpeichern: 
   //hier kommt das Speichern rein!
   if (!doEingabeSpeichern(this, "WriterData.txt", etEingabe1.getText().toString())) { 
      //Speichern hat nicht geklappt!! Wir koennten hier was tun
      ; 
   }
   break;

Das erledigen wir mit obigen Zeilen. Das Ergebnis vom speichern werten wir aus – wir tun jedoch noch nichts. Hier müsste im Ernstfall dann irgend etwas eingebaut werden, was passieren soll, wenn ein Speicherfehler eingebaut wird. Zum Beispiel eine Meldung an den Benutzer.

Bei den Zeilen bitte darauf achten:

if (!doEingabeSpeichern …..)

ist identisch mit der Formulierung

if (doEingabeSpeichern …. == false)

Das Ausrufezeichen am Anfang der Klammer signalisiert, wenn der Ausdruck in der Klammer false ist, dann soll was geschehen. Die untere Schreibweise ist nur die Langschreibweise – aber manchmal übersichtlicher. Ihr könnt verwenden, was euch Spaß macht!

Alternativ könnte es auch so aussehen – mit kleiner Erfolgsmeldung für den Benutzer:

case R.id.buttonSpeichern: 
   //hier kommt das Speichern rein! 
   if (doEingabeSpeichern(this, "WriterData.txt", etEingabe1.getText().toString())) { 
      //Speicher OK. Meldung anzeigen und weiter
      Toast.makeText(this, "Datei gespeichert!", Toast.LENGTH_LONG).show();
      etEingabe1.setText("");  
      etEingabe1.requestFocus(); 
   }
break;

 

Fertig!

Wenn ihr die App jetzt ausführt, könnt ihr den Button Speichern drücken und die Eingaben werden gespeichert.

Der Speicherort

Wir drücken den Speichern Button, aber es passiert nichts. Speichert unser Programm wirklich? Im wahren Leben einer App können wir jetzt den Returnwert als Ergebnis auswerten und dem Benutzer eine OK oder Fehlermeldung anzeigen. Doch das entbindet uns als Programmierer nicht von der Pflicht zu prüfen, ob das mit dem Speichern auch richtig aussieht!

Um uns die Speicherdatei anzusehen, müssen wir zuerst herausfinden, wo Android diese Datei hin speichert. Mit der Log Anzeige

Log.d ("Hier gespeichert:", myFile.toString());

wird uns der Inhalt der myFile Variable im Logcat ausgegeben. Unsere Log Ausgaben im Device Emulator finden wir unter RUN (Android Studio Chipmunk) oder im Logcat (Android Studio Giraffe).

Unter „Hier gespeichert“ finden wir den Eintrag:

/data/user/0/de.terminalsystems.aewriter/files/WriterData.txt

und kennen nun das Verzeichnis, das Android für unsere Datei gewählt hat. Hier erschließt sich auch, was mit Context gemeint war, den wir an bestimmten Stellen im Code eingegeben mussten. Android verwaltet für jede App eigene Speicherbereiche und nimmt Context dazu, diese zu identifizieren.

Device Explorer im Device Emulator starten

Wollen wir uns die Datei ansehen: Im Device Emulator für das aktive Gerät das Speichersystem öffnen:

Es öffnet sich der Device Explorer und ihr könnt im Dateisystem euer Simulation blättern. Navigiert zum betroffenen Verzeichnis und schaut euch die Datei an:

Doppelklick auf unsere gespeicherte Datei zeigt den Inhalt und alle unsere bisher gespeicherten Texte:

Speichern an öffentlichen Orten – Intents

Ich befürchte, das wird jetzt etwas langweilig – ist aber ungemein wichtig! Ich hörte bereits einige professionelle Android Programmierer stöhnen: Das Übel begann mit Android Version 11.

Bis Android10 konnten wir in in unseren Anwendungen Dateinamen und Speicherorte mehr oder minder frei wählen und Dateien lesen und schreiben, wo und wie wir wollten. Wir konnten auch bereits per Upload übertragene Dateien einfach auslesen und verarbeiten, ohne dass der Benutzer, sich um Dateinamen und Speicherorte kümmern musste.

Ab Android11 hat Google ein Sicherheitskonzept eingeführt, das es untersagt, dass eine von einer Anwendung erstellte Datei automatisch programmgesteuert von einer anderen Anwendung verwendet werden kann. Konkret: eine Anwendung soll automatisch nur noch in dem ihr zugewiesenen Bereich speichern und lesen. Wenn ihr Dateien irgendwo anders hin speichern oder auslesen wollt, soll der Benutzer das freigeben und Dateiname und Ort auswählen bzw. bestätigen.

Über Sinn und Unsinn dieses Sicherheitskonzeptes will ich nicht urteilen. Es bereitet jedoch professionellen Enterprise Programmierern in Business Anwendungen etwas Kopfzerbrechen und verlangt vom Benutzer zusätzliche Schritte, die seinen Tagesablauf nicht einfacher gestalten.

In der Praxis hatten wir oftmals Anwendungen bei denen im lfd Betrieb Dateien ausgetauscht wurden, ohne dass der Benutzer informiert wurde. Stellt Euch vor Ihr sollt Pakete ausliefern und bei jeder Auslieferung den Barcode scannen und dann den Name desjenigen eingeben, der das Paket in Empfang nimmt.  Da kann es schnell vorkommen, dass noch während der Tour ein Download erfolgt, der die erledigten Pakete von Android Device auf einen Computer zieht – und / oder ein Upload, der neue Paketadressen bereit stellt.  Bis Android10 konnte ein solcher Transfer einfach im Hintergrund erfolgen, ohne dass der Benutzer sich darum kümmern musste. Er hatte einfach immer aktuelle Daten. Ab Android11 muss er neue Daten jedoch erst einmal manuell einlesen und seiner Anwendung bekannt machen, damit er sie nutzen kann. Kurz: Es ist nun bei Dateien ein Benutzereingriff notwendig und das ist bei professionellen Anwendungen zur Datenerfassung nicht immer von Vorteil.

Für uns Programmierer heißt das: wir brauchen eine Logik bei der der Anwender etwa auswählt und wir müssen auf seine Auswahl reagieren. Das Speichern und Lesen von Daten geschieht dabei nicht mehr über programmgesteuerte Java Funktionen auf File Ebene, sondern über sogenannte INTENTS.

Generell sind Intents erst mal eine gute Sache. Sie geben euch Zugriff auf die Ressourcen des Smartphones, z.B. Camera, GPS, Sensoren oder halt das Dateisystem. Jedes Intent muss aber ab Android11 immer vom Benutzer aktiviert und freigegeben werden.

In unserem kleinen Beispiel: wenn ihr mit dem Button Speichern in eine Datei speichert, werden alle Eingaben immer direkt an die vorhandene Datei angefügt und die Datei wird irgendwo im internen Android Dateisystem hinterlegt. Wo genau haben vor im vorherigen Kapitel geklärt!

Jetzt wollen wir die Möglichkeit schaffen, diese interne Datei zu exportieren, so dass der Benutzer sie für andere Anwendungen oder für den Download mittels USB Kabel freigeben kann. Der Export soll über ein Intent erfolgen, bei dem der Benutzer Dateiname und Ort wählen kann.

Zuerst erweitern wir unsere App in der Layout Datei um einen Button buttonExport. Wir fügen den Button als dritten Button im Horizontalen Layout ein.

<LinearLayout 
android:layout_width="match_parent" 
android:layout_height="wrap_content" 
android:orientation="horizontal"> 

<Button 
android:id="@+id/buttonClear" 
android:layout_width="120dp" 
android:layout_height="wrap_content" 
android:text="Clear" /> 

<Button 
android:id="@+id/buttonSpeichern" 
android:layout_width="120dp" 
android:layout_height="wrap_content" 
android:layout_marginLeft="10dp" 
android:text="Speichern" /> 

<Button 
android:id="@+id/buttonExport" 
android:layout_width="120dp" 
android:layout_height="50dp" 
android:layout_marginLeft="10dp" 
android:text="Export" /> 

</LinearLayout>

In onCreate registrieren wir den Listener für den neuen Button:

findViewById(R.id.buttonExport).setOnClickListener(this);

Nutzung des Intent einbauen

Unser onClick Listener bekommt den Code, was zu tun ist, wenn der Button betätigt wird.

Hier arbeiten wir erstmals mit einem Intent, dessen Code wir vorher selbst vergeben. Das Intent ist Erzeugen eines Dokumentes = Action_Create_Document. Der Dateiname / Titel des Dokumentes ist uns egal. Den kann der Benutzer eingeben. Das Intent zum Speichern von Daten liefert uns keinen File Konstruktor, sondern eine URI. Wir brauchen also jetzt eine Speicherfunktion, die Daten in eine Datei speichert, die mit einer URI definiert wurde. Infos zur Uri

case R.id.buttonExport: 
   Intent intent = new Intent((Intent.ACTION_CREATE_DOCUMENT)); 
   intent.addCategory(Intent.CATEGORY_DEFAULT); 
   intent.setType("text/plain"); 
   intent.putExtra(Intent.EXTRA_TITLE, ""); 
   startActivityForResult(intent, REQUESTCODE_SAVE); 
   break;

Neue Klassenvariable

Neu eingeführt haben wir eine Variable REQUESTCODE_SAVE. Dieses bezeichnet einen ID Wert, den vor vorher global als private static für die ganze Klasse definiert haben. Wir können jedem Intent einen willkürlichen Requestcode zuweisen, die wir dann später auswerten und so erkennen, welches Intent der Benutzer aktiviert hat. Ich habe mal drei mögliche Requestcodes aufgenommen, die wir häufig verwenden:

//----------------------------------------------------------------------
 public class MainActivity extends AppCompatActivity implements View.OnClickListener { 

private static EditText etEingabe1; 

private static final int REQUESTCODE_SAVE = 123; //Requestcode f. Export / Save
private static final int REQUESTCODE_LOAD = 125; //Requestcode f. Import / Laden
private static final int REQUESTCODE_CAMERA = 160; //Requestcode f. Camera / Scanner

Android mit missionarischem Eifer

An dieser Stelle werdet ihr mit einer Google Besonderheit vertraut gemacht: Google ist ein großes Unternehmen und beschäftigt wohl viele Programmierer, die mehr oder weniger eifrig am Android Code und Verbesserungen arbeiten.

Leider sehen die ihre Arbeit manchmal auch mit etwas erzieherischen Charakter. Bewährte Methoden werden manchmal als veraltet dargestellt und durch neue ersetzt, deren Nutzung sich in vielen Fällen aber als komplizierter erweist und / oder den Code unübersichtlicher gestalten.

Glücklicherweise funktionieren oftmals die alten Methoden in vielen Fällen noch – sie werden jedoch vom Android Studio als veraltet markiert.

In meinem Kochbuch geht es mir nicht darum den schönsten Java Code zu erzeugen. In der Programmierung ist es wie im richtigen Leben: viele Wege führen nach Rom. Der Praktiker will schnelle, funktionale Ergebnisse sehen, deshalb ignoriere ich einen Hinweis auf veraltet an dieser Stelle und benutze die mir bekannte Methoden weiterhin.

In diesem Fall ist die Methode startAcitivityForResult betroffen, die von meinem Studio angemeckert – aber trotz Anmeckerung ausgeführt wird. Wenn Ihr wissen wollt, wir ihr die ersetzt: benutzt Google! Oder ich schreibe irgendwann einmal ein Kapitel in dem das erklärt wird.

Was hat der Benutzer gemacht? Ein Intent abfragen

Anschließend bauen wir uns mit onActivityResult eine Methode, die uns informiert, ob / welche Intents aktiviert wurden. Sollte das Intent Daten speichern basteln sein, wird eine Speicherroutine aufgerufen, die unsere interne Daten in eine Datei mit Namen und Speicherort nach Angaben des Benutzers exportiert.

//----------------------------------------------------------------------
 public void onActivityResult(int requestCode, int resultCode, Intent resultData) { 
   //Intent auswerten

   super.onActivityResult(requestCode, resultCode, resultData); 
 
   //RequestCode Save / Export
   if (requestCode == REQUESTCODE_SAVE && resultCode == Activity.RESULT_OK) { 
      Uri uri = null; 
      if (resultData != null) { 
         uri = resultData.getData(); 
         doExportDatei(uri, "WriterData.txt"); 
      } 
   } 
   //Andere RequestCodes hier einbauen
 }

Speichern mit Intents – die URI

Und die Speicherroutine

//----------------------------------------------------------------------
 private void doExportDatei (Uri myZielUri, String myQuelldatei) {

   try {

      OutputStream stream = getContentResolver().openOutputStream(myZielUri);
      PrintWriter writer = new PrintWriter(stream);

      File myFileQuelle = new File (this.getFilesDir(), myQuelldatei);
      if (!myFileQuelle.exists()) {
         //Datei nicht vorhanden!
         Toast.makeText(this, "Datei nicht vorhanden!", Toast.LENGTH_LONG).show();
         return;
      }
      //Read text from file
      StringBuilder text = new StringBuilder();

      try {
         FileInputStream fileIn = new FileInputStream(myFileQuelle);
         InputStreamReader InputRead = new InputStreamReader(fileIn, "ISO_8859_1");
         BufferedReader br = new BufferedReader(InputRead);

         String line;

         while ((line = br.readLine()) != null) {
            text.append(line);
            text.append('\n');
         }
         br.close();
      }
      catch (IOException e) {
         //You'll need to add proper error handling here
         String x2 = e.getMessage();
         Log.e ("Error_Show050", x2);
         Toast.makeText(this, x2, Toast.LENGTH_LONG).show();
         e.printStackTrace();
      }

      writer.write(text.toString());
      writer.flush();
      stream.close();

      //Quelle nach Erfolg loeschen
      myFileQuelle.delete ();

      //Meldung an Benutzer
      Toast.makeText(this, "Datei exportiert!", Toast.LENGTH_LONG).show();

   } catch (IOException e) {
      Log.e(getLocalClassName(), "caught IOException", e);
   }
}

Finaler Text im Device Manager

Fertig. Das war es. Jetzt via Device Manager die Anwendung aufrufen, Button Export und Datei mit Namen MeineNotizen.txt ins Download Verzeichnis speichern.

Um unser Werk anzusehen: wir aktivieren unter Android in den Datei Explorer, wechseln in das Verzeichnis Download und starten durch Doppellkick den Viewer für diese Datei.

Jetzt haben wir unsere Daten unter Android zur freien Verfügung. Wir können sie per USB Kabeltransfer, Bluetooth an einen Computer laden, in die Cloud transferieren oder was auch immer damit veranstalten.

Video zum Text


Hinweis: Dieser Text ist ein Preview. Die überarbeitete Fassung gibt es bei uns als PDF!


Text und Entwurf. (c) AE SYSTEME Testcenter, Hans-J. Walter
Hans-J. Walter ist Programmierer für Windows DOT.NET / C# und Android und als eingetragener, unabhängiger Journalist verantwortlich für Fachberichte und Schulungstexte über Technik u. Entwicklung. hjw@terminal-systems.de

Für diese und alle nachfolgenden Seiten gilt ebenso der obligatorische Hinweis: Alle Angaben ohne Gewähr. Bilder und Codes zeigen Beispiele. Diese Beschreibung bezieht sich auf unsere Installation und stellt keine Bewertung der verwendeten Techniken da. Fehler und Irrtümer vorbehalten!

Schreibe einen Kommentar