{"id":2963,"date":"2023-11-24T00:11:22","date_gmt":"2023-11-23T23:11:22","guid":{"rendered":"https:\/\/www.art-events.de\/weblog\/?p=2963"},"modified":"2023-11-28T17:38:22","modified_gmt":"2023-11-28T16:38:22","slug":"ae-android-kochbuch-notizen-app-reloaded","status":"publish","type":"post","link":"https:\/\/www.art-events.de\/weblog\/ae-android-kochbuch-notizen-app-reloaded\/","title":{"rendered":"AE Android Kochbuch: Notizen App. Reloaded"},"content":{"rendered":"<p>Unser AE Android Kochbuch Teil 3A: Notizen App Reloaded. Schreiben. Speichern. Laden. Vorlesen lassen.<\/p>\n<p>In Teil 3 pr\u00e4sentierte ich eine kleine Notizbuch App, die aber nur eingeschr\u00e4nkt nutzbar war. Daher gibt es hier Teil 3A &#8211; die Notizen App Reloaded! In diesem Teil widme ich mich dem gleichen Thema, aber nun basteln wir die App so, dass man sie auch sinnvoll verwenden kann. Und wer es eilig hat: die App bekommt auch eine Funktion, dass sie euch eure Notizen vorlesen kann!<\/p>\n<p><a href=\"https:\/\/www.art-events.de\/weblog\/wp-content\/uploads\/2023\/11\/word-image-2963-4.png\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-medium wp-image-2967\" src=\"https:\/\/www.art-events.de\/weblog\/wp-content\/uploads\/2023\/11\/word-image-2963-4-147x300.png\" alt=\"\" width=\"147\" height=\"300\" srcset=\"https:\/\/www.art-events.de\/weblog\/wp-content\/uploads\/2023\/11\/word-image-2963-4-147x300.png 147w, https:\/\/www.art-events.de\/weblog\/wp-content\/uploads\/2023\/11\/word-image-2963-4.png 393w\" sizes=\"auto, (max-width: 147px) 100vw, 147px\" \/><\/a><\/p>\n<p>Unser AE Android Kochbuch f\u00fcr 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 \u2013 ich empfehle die ersten Kapitel vom Buch Java ist auch nur eine Insel oder andere Literatur, die euch in solche grundlegenden Dinge einf\u00fchrt.<\/p>\n<h3>Die Ausgangslage<\/h3>\n<p>Android Studio aktiv. Ich benutze jetzt die gerade aktuelle Version Giraffe. Neues Java Projekt Writer ist angelegt.<\/p>\n<h3><a id=\"post-2963-__RefHeading___Toc2308_4174826217\"><\/a>Layout Datei<\/h3>\n<p>Wie auch schon in den Teilen davor beginnen wir mit der Layout Gestaltung.<\/p>\n<h4><a id=\"post-2963-__RefHeading___Toc1698_3831981344\"><\/a>Eingabefenster<\/h4>\n<p>Prim\u00e4r verwenden wir ein Element EditText, das wir auf Multiline setzen, damit wir mehrere Zeilen eingeben k\u00f6nnen. Nat\u00fcrlich vergeben wir auch eine id, damit wir den Inhalt von EditText vom Java Programm aus lesen oder ver\u00e4ndern k\u00f6nnen.<\/p>\n<pre>&lt;EditText \r\nandroid:id=\"@+id\/editTextText\" \r\nandroid:layout_width=\"match_parent\" \r\nandroid:layout_height=\"300dp\" \r\nandroid:background=\"@drawable\/drawback\" \r\nandroid:gravity=\"top\" \r\nandroid:scrollHorizontally=\"false\" \r\nandroid:scrollbars=\"vertical\" \r\nandroid:inputType=\"textMultiLine\" \r\nandroid:text=\"\" \/&gt;<\/pre>\n<h4><a id=\"post-2963-__RefHeading___Toc2310_4174826217\"><\/a>Buttons f\u00fcr Benutzeraktivit\u00e4ten<\/h4>\n<p>Dem Benutzer spendieren wir sinnvollerweise die Buttons:<\/p>\n<p><strong>-) Button Notizen laden<\/strong><\/p>\n<p>Der Benutzer soll aus gespeicherten Notizen ausw\u00e4hlen und diese laden.<\/p>\n<p><strong>-) Button Notizen speichern<\/strong><\/p>\n<p>Notizen k\u00f6nnen als Datei gespeichert werden.<\/p>\n<p><strong>-) Button Notiz vorlesen<\/strong><\/p>\n<p>Die App soll eine Notiz vorlesen.<\/p>\n<p><strong>-) Notiz Fenster l\u00f6schen<\/strong><\/p>\n<p>Der Inhalt im Notiz Fenster soll komplett entfernt werden.<\/p>\n<p>Die ersten drei Buttons basteln wir in der Layout Datei in gewohnter Form direkt unterhalb dem Eingabefenster. Das Ganze kapseln wir in einem horizontalem Layout, damit die Buttons nebeneinander stehen.<\/p>\n<pre>&lt;LinearLayout \r\nandroid:layout_width=\"match_parent\" \r\nandroid:layout_height=\"wrap_content\" \r\nandroid:orientation=\"horizontal\"&gt; \r\n\r\n&lt;Button \r\nandroid:id=\"@+id\/buttonLaden\" \r\nandroid:layout_width=\"120dp\" \r\nandroid:layout_height=\"wrap_content\" \r\nandroid:text=\"Laden\" \/&gt; \r\n\r\n&lt;Button \r\nandroid:id=\"@+id\/buttonSpeichern\" \r\nandroid:layout_width=\"120dp\" \r\nandroid:layout_height=\"wrap_content\" \r\nandroid:layout_marginLeft=\"10dp\" \r\nandroid:text=\"Speichern\" \/&gt; \r\n\r\n&lt;Button \r\nandroid:id=\"@+id\/buttonVorlesen\" \r\nandroid:layout_width=\"120dp\" \r\nandroid:layout_height=\"wrap_content\" \r\nandroid:layout_marginLeft=\"10dp\" \r\nandroid:text=\"Vorlesen\" \/&gt; \r\n&lt;\/LinearLayout&gt;<\/pre>\n<h4><a id=\"post-2963-__RefHeading___Toc2312_4174826217\"><\/a>Der Imagebutton zum L\u00f6schen<\/h4>\n<p>Zum L\u00f6schen des Eingabefensters verwenden wir das Element ImageButton, weil ein kleines Piktogramm ausreichen sollte, um die Funktion des Buttons klar zu machen.<\/p>\n<p>Den Imagebutton zieht ihr in der Design Ansicht aus der Palette Buttons einfach wie gewohnt in eure Layout Ansicht.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"311\" height=\"169\" class=\"wp-image-2964\" src=\"https:\/\/www.art-events.de\/weblog\/wp-content\/uploads\/2023\/11\/word-image-2963-1.png\" srcset=\"https:\/\/www.art-events.de\/weblog\/wp-content\/uploads\/2023\/11\/word-image-2963-1.png 311w, https:\/\/www.art-events.de\/weblog\/wp-content\/uploads\/2023\/11\/word-image-2963-1-300x163.png 300w\" sizes=\"auto, (max-width: 311px) 100vw, 311px\" \/><\/p>\n<p>Damit der Button ein Bild bekommt: Das Bild f\u00fcr den Imagebutton w\u00e4hlen wir in Ressource. In meinem Fall soll das einfach nur das \u00fcbliche X sein.<\/p>\n<p>Um das Bild f\u00fcr den Imagebutton zu w\u00e4hlen: einfach den Cursor im Projektfenster auf RES platzieren und mit der rechten Maustaste bekommt ihr ein Auswahlmen\u00fc NEW um dann ein Bild als Vector Asset auszuw\u00e4hlen.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"741\" height=\"248\" class=\"wp-image-2965\" src=\"https:\/\/www.art-events.de\/weblog\/wp-content\/uploads\/2023\/11\/word-image-2963-2.png\" srcset=\"https:\/\/www.art-events.de\/weblog\/wp-content\/uploads\/2023\/11\/word-image-2963-2.png 741w, https:\/\/www.art-events.de\/weblog\/wp-content\/uploads\/2023\/11\/word-image-2963-2-300x100.png 300w\" sizes=\"auto, (max-width: 741px) 100vw, 741px\" \/><\/p>\n<p>Klickt dann in der Maske einfach in das Feld Clip Art und ihr erhalten eine \u00dcbersicht \u00fcber die verf\u00fcgbaren Clip Arts aus denen ihr dann ausw\u00e4hlen k\u00f6nnt. Ihr k\u00f6nnt nun durch die Cllip Arts scrollen oder im Suchfeld Clear angeben. Je naachdem, wie ihr vorgehen woll. Am Ende sollte es dann mal so aussehen und ihr k\u00f6nnt das Bild als Ressource \u00fcbernehmen.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"702\" height=\"541\" class=\"wp-image-2966\" src=\"https:\/\/www.art-events.de\/weblog\/wp-content\/uploads\/2023\/11\/word-image-2963-3.png\" srcset=\"https:\/\/www.art-events.de\/weblog\/wp-content\/uploads\/2023\/11\/word-image-2963-3.png 702w, https:\/\/www.art-events.de\/weblog\/wp-content\/uploads\/2023\/11\/word-image-2963-3-300x231.png 300w\" sizes=\"auto, (max-width: 702px) 100vw, 702px\" \/><\/p>\n<p>Last not least die Ressource in der XML Datei beim Image Button einbauen:<\/p>\n<pre>&lt;ImageButton \r\nandroid:id=\"@+id\/imageButton_clrText\" \r\nandroid:layout_width=\"40dp\" \r\nandroid:layout_height=\"40dp\" \r\napp:srcCompat=\"@drawable\/baseline_clear_24\" \/&gt;<\/pre>\n<h4><a id=\"post-2963-__RefHeading___Toc2314_4174826217\"><\/a>Layout komplett<\/h4>\n<p>Somit erhalten wir die Layout Datei:<\/p>\n<pre><em>&lt;?<\/em>xml version=\"1.0\" encoding=\"utf-8\"<em>?&gt;\r\n<\/em> &lt;LinearLayout xmlns:android=\"http:\/\/schemas.android.com\/apk\/res\/android\" \r\nxmlns:app=\"http:\/\/schemas.android.com\/apk\/res-auto\" \r\nxmlns:tools=\"http:\/\/schemas.android.com\/tools\" \r\nandroid:layout_width=\"match_parent\" \r\nandroid:layout_height=\"match_parent\" \r\nandroid:orientation=\"vertical\" \r\ntools:context=\".MainActivity\"&gt; \r\n\r\n&lt;TextView \r\nandroid:layout_width=\"wrap_content\" \r\nandroid:layout_height=\"wrap_content\" \r\nandroid:text=\"Willkommen im AE Mini Writer\" \/&gt; \r\n\r\n&lt;ImageButton \r\nandroid:id=\"@+id\/imageButton_clrText\" \r\nandroid:layout_width=\"40dp\" \r\nandroid:layout_height=\"40dp\" \r\napp:srcCompat=\"@drawable\/baseline_clear_24\" \/&gt; \r\n\r\n&lt;EditText \r\nandroid:id=\"@+id\/editTextText\" \r\nandroid:layout_width=\"match_parent\" \r\nandroid:layout_height=\"300dp\" \r\nandroid:background=\"@drawable\/drawback\" \r\nandroid:gravity=\"top\" \r\nandroid:scrollHorizontally=\"false\" \r\nandroid:scrollbars=\"vertical\" \r\nandroid:inputType=\"textMultiLine\" \r\nandroid:text=\"\" \/&gt; \r\n\r\n&lt;LinearLayout \r\nandroid:layout_width=\"match_parent\" \r\nandroid:layout_height=\"wrap_content\" \r\nandroid:orientation=\"horizontal\"&gt; \r\n\r\n&lt;Button \r\nandroid:id=\"@+id\/buttonLaden\" \r\nandroid:layout_width=\"120dp\" \r\nandroid:layout_height=\"wrap_content\" \r\nandroid:text=\"Laden\" \/&gt; \r\n\r\n&lt;Button \r\nandroid:id=\"@+id\/buttonSpeichern\" \r\nandroid:layout_width=\"120dp\" \r\nandroid:layout_height=\"wrap_content\" \r\nandroid:layout_marginLeft=\"10dp\" \r\nandroid:text=\"Speichern\" \/&gt; \r\n\r\n&lt;Button \r\nandroid:id=\"@+id\/buttonVorlesen\" \r\nandroid:layout_width=\"120dp\" \r\nandroid:layout_height=\"wrap_content\" \r\nandroid:layout_marginLeft=\"10dp\" \r\nandroid:text=\"Vorlesen\" \/&gt; \r\n\r\n&lt;\/LinearLayout&gt; \r\n\r\n&lt;\/LinearLayout&gt;<\/pre>\n<h3><a id=\"post-2963-__RefHeading___Toc1927_247567268\"><\/a>Der Java Code<\/h3>\n<p>Wechseln wir jetzt in die Datei MainAcitivity.java und f\u00fcllen die App mit Leben.<\/p>\n<p>private static EditText <em>etEingabe1<\/em>;<\/p>\n<pre><em>\/\/----------------------------------------------------------------------\r\n<\/em> @Override \r\nprotected void onCreate(Bundle savedInstanceState) { \r\n  super.onCreate(savedInstanceState); \r\n  setContentView(R.layout.<em>activity_main<\/em>); \r\n\r\n<em>  \/\/EditText Variable init - weil wir sie ueberall nutzen\r\n  etEingabe1 <\/em>= findViewById(R.id.<em>editTextText<\/em>); \r\n\r\n<em>  \/\/Listener fuer Buttons registrieren\r\n<\/em>  findViewById(R.id.<em>imageButton_clrText<\/em>).setOnClickListener(this); \r\n  findViewById(R.id.<em>buttonLaden<\/em>).setOnClickListener(this); \r\n  findViewById(R.id.<em>buttonSpeichern<\/em>).setOnClickListener(this); \r\n  findViewById(R.id.<em>buttonVorlesen<\/em>).setOnClickListener(this); \r\n\r\n<em>  \/\/am Start: Cursor in das Textfeld setzen\r\n  etEingabe1<\/em>.requestFocus(); \r\n}<\/pre>\n<p>Das Eingabefenster als editText Element wird der EditText Variable etEingabe1 zugewiesen. Die Variable wird au\u00dferhalb der Methode deklariert, damit wir sie in der ganzen Klasse nutzen k\u00f6nnen, ohne sie jedes Mal neu zuweisen zu m\u00fcssen. Die Zuweisung hingegen findet in der Methode onCreate statt. Last not least stellen wir den Cursor ins Eingabefenster, damit der Benutzer gleich tippen kann.<\/p>\n<p>In der Methode onCreate registrieren wir die vier Buttons jeweils mit einem onClickListener. Ob ImageButton oder normaler Button spielt dabei keine Rolle. Alle Button Elemente werden identisch registriert. Wir erhalten dann den onClick Listener, bei dem wir die Aktionen f\u00fcr jeden Button eintragen.<\/p>\n<pre><em>\/\/----------------------------------------------------------------------\r\n<\/em> @Override \r\npublic void onClick(View v) { \r\n<em>  \/\/Unser Eingabe Listener\r\n<\/em><i> <\/i> switch (v.getId()) {\r\n    \/\/hier tragen wir die Aktionen f\u00fcr die Buttons ein\r\n }\r\n}<\/pre>\n<h4><a id=\"post-2963-__RefHeading___Toc1929_247567268\"><\/a>Image Button L\u00f6schen<\/h4>\n<p>Mit dem kleinen Imagebutton kann ich das Eingabefenster jederzeit l\u00f6schen. Grafik und Design des Buttons finden sich in der XML Layout Datei. Im Java Code lege ich die Aktionen fest, wenn der Button geklickt wird. Konkret geschieht das in der switch Schleife in der onClick Methode. Dort hinterlege ich die Aktionen, was passiert, wenn der Benutzer den Image Button bet\u00e4tigt.<\/p>\n<pre><em>\/\/----------------------------------------------------------------------\r\n<\/em> @Override \r\npublic void onClick(View v) { \r\n<em>  \/\/Unser Eingabe Listener\r\n<\/em>  switch (v.getId()) { \r\n    case R.id.<em>imageButton_clrText<\/em>: \r\n<em>    \/\/Button CLEAR\r\n    etEingabe1<\/em>.setText(\"\"); \r\n<em>    etEingabe1<\/em>.requestFocus(); \r\n    break;\r\n  }\r\n}<\/pre>\n<h4><a id=\"post-2963-__RefHeading___Toc1700_3831981344\"><\/a>Daten speichern und laden von \u00f6ffentlichen Orten. Intents<\/h4>\n<p>Bevor wir uns jetzt mit dem Buttons Speichern und Laden besch\u00e4ftigen, m\u00fcssen wir \u00fcber Intents reden. Intents sind die Google Antwort, damit wir per Software auf alle m\u00f6glichen Ressourcen des Smartphone zugreifen und trotzdem die von Google erw\u00fcnschten Sicherheitsstandards einhalten. Hierbei gilt: Eine Anwendung darf nicht ohne Erlaubnis auf Ressourcen des Smartphone zugreifen, sondern ben\u00f6tigt immer eine Erlaubnis oder Aktion des Benutzers.<\/p>\n<p>In unserem Fall werden wir Intents verwenden, um Daten zu speichern und Daten zu lesen. Dateiname der Datei sowie Speicherort kann der Benutzer jeweils individuell vergeben. Wegen der unterschiedlichen Rechte auf verschiedenen Verzeichnissen empfehle ich, das \/Downloads Verzeichnis zu verwenden. Wer mag, kann sich hier ein weiteres Unterverzeichnis anlegen.<\/p>\n<p>In Intent besteht aus Sicht des Programms immer aus folgenden Aktionen:<\/p>\n<p>1) Request Code f\u00fcr verschiedene Intents definieren<\/p>\n<p>2) Ein Intent wird aufgerufen<\/p>\n<p>3) Ein Intent wurde aktiviert und muss ausgewertet werden<\/p>\n<p><strong>Request Code f\u00fcr Intent definierern<\/strong><\/p>\n<p>Damit verschiedene Intents voneinander trennen k\u00f6nnen, bekommen sie beim Aufruf einen beliebigen Request Code mitgeteilt, den ihr dann sp\u00e4ter in der Auswertung abfragen k\u00f6nnt. Zu diesem Zweck vergebe ich am Anfang der Klasse f\u00fcr verschiedene Intents unterschiedliche Id Werte, z.B:<\/p>\n<pre><em>\/\/----------------------------------------------------------------------\r\n<\/em> public class MainActivity extends AppCompatActivity implements View.OnClickListener { \r\n\r\nprivate static final int <em>REQUESTCODE_SAVE <\/em>= 123; <em>\/\/Requestcode f. Export \/ Save\r\n<\/em>private static final int <em>REQUESTCODE_LOAD <\/em>= 125; <em>\/\/Requestcode f. Import \/ Laden\r\n<\/em>private static final int <em>REQUESTCODE_CAMERA <\/em>= 160; <em>\/\/Requestcode f. Camera \/ Scanner<\/em><\/pre>\n<p>Die ID Werte sind rein willk\u00fcrlich gew\u00e4hlt. Wichtig ist nur, dass ihr sie unterscheiden k\u00f6nnen. Also wenn Save und Camera den gleichen Wert aufweisen, wird es ein Problem geben.<\/p>\n<p><strong>Intent aufrufen<\/strong><\/p>\n<p>Den Aufruf eines Intentes k\u00f6nnen ihr in eurem Java Code dort unterbringen, wo ihr es gerne habt. Also z.b. in der onClick Methode, wenn ein Button bet\u00e4tigt wird. Abh\u00e4ngig von dem, was der Benutzer machen soll, bekommen die Intents unterschiedliche Start Werte. Nachstehendes Beispiel zeigt wie man das Intent zum Anlegen und Speichern einer reinen Text Datei aufruft:<\/p>\n<pre>Intent intent = new Intent(Intent.<em>ACTION_CREATE_DOCUMENT<\/em>); \r\nintent.addCategory(Intent.<em>CATEGORY_DEFAULT<\/em>); \r\nintent.setType(\"text\/plain\"); \r\nintent.putExtra(Intent.<em>EXTRA_TITLE<\/em>, \"\"); \r\nstartActivityForResult(intent, <em>REQUESTCODE_SAVE<\/em>);<\/pre>\n<p>Es versteht sich vermutlich, dass das Intent f\u00fcr die eingebaute Camera etwas anders aussehen wird. Ihr m\u00fcsst euch also jedes Mal \u00fcber das Intent informieren, wenn ihr eins verwenden wollt.<\/p>\n<p><strong>Intent auswerten<\/strong><\/p>\n<p>Das Abfragen von Intents geschieht hingegen in der Methode onAcitivityResult. Diese Methode ist f\u00fcr alle Intents da, d.h. ihr m\u00fcsst selbst abfragen, welches Intent gerade durchgef\u00fchrt wurde. Ob z.B. der Benutzer die Camera verwendet hat, um Barcode zu lesen oder ob eine Datei gespeichert werden soll. Beispiel f\u00fcr die Methode, wobei ich bereits auf die Intents Datei speichern und Datei laden abfrage:<\/p>\n<pre><em>\/\/----------------------------------------------------------------------\r\n<\/em> public void onActivityResult(int requestCode, int resultCode, Intent resultData) { \r\n<em>  \/\/Intent auswerten\r\n\r\n<\/em>  super.onActivityResult(requestCode, resultCode, resultData); \r\n\r\n<em>  \/\/Intent: Datei speichern\r\n<\/em>  if (requestCode == <em>REQUESTCODE_SAVE <\/em>&amp;&amp; resultCode == Activity.<em>RESULT_OK<\/em>) { \r\n<em>    \/\/hier kommt der Code zum Speichern rein<\/em> \r\n  } \r\n\r\n<em>  \/\/Intent: Datei laden\r\n<\/em>  if (requestCode == <em>REQUESTCODE_LOAD <\/em>&amp;&amp; resultCode == Activity.<em>RESULT_OK<\/em>) {\r\n    <em>\/\/hier kommt der Code zum Laden rein<\/em> \r\n  } \r\n}<\/pre>\n<h4><a id=\"post-2963-__RefHeading___Toc1931_247567268\"><\/a>Button Notiz speichern<\/h4>\n<p>Genauso wie oben geht es auch mit dem Button Notiz. Hier soll eine Speicherroutine aktiviert werden, bei der Benutzer Dateinamen und Speicherort ausw\u00e4hlen kann, wenn er seine Notiz speichert. Die Aktionen f\u00fcr den Button werden daher auch hier wieder in der switch Schleife in der Methode onClick eingetragen. Da ich \u00fcber Intents speichern will \u2013 wie oben angef\u00fchrt:<\/p>\n<pre><em>\/\/----------------------------------------------------------------------\r\n<\/em> @Override \r\npublic void onClick(View v) { \r\n<em>  \/\/Unser Eingabe Listener\r\n\r\n<\/em>  Intent intent = new Intent(); \r\n\r\n  switch (v.getId()) { \r\n... \r\n\r\n    case R.id.<em>buttonSpeichern<\/em>: \r\n      intent = new Intent(Intent.<em>ACTION_CREATE_DOCUMENT<\/em>); \r\n      intent.addCategory(Intent.<em>CATEGORY_DEFAULT<\/em>); \r\n      intent.setType(\"text\/plain\"); \r\n      intent.putExtra(Intent.<em>EXTRA_TITLE<\/em>, \"\"); \r\n      startActivityForResult(intent, <em>REQUESTCODE_SAVE<\/em>); \r\n      break;\r\n\r\n  }\r\n}<\/pre>\n<p>Zum Abfragen des Intents nutzen wir unsere onActivityResult Methode.<\/p>\n<pre><em>\/\/----------------------------------------------------------------------\r\n<\/em> public void onActivityResult(int requestCode, int resultCode, Intent resultData) { \r\n<em>  \/\/Intent auswerten\r\n\r\n<\/em>  super.onActivityResult(requestCode, resultCode, resultData); \r\n\r\n<em>  \/\/Intent: Datei speichern\r\n<\/em>  if (requestCode == <em>REQUESTCODE_SAVE <\/em>&amp;&amp; resultCode == Activity.<em>RESULT_OK<\/em>) { \r\n    Uri uri = null; \r\n    if (resultData != null) { \r\n      uri = resultData.getData(); \r\n      doSaveData2Uri (<em>etEingabe1<\/em>, uri); \r\n    } \r\n  } \r\n}<\/pre>\n<p>Intents mit Datei Operationen liefern uns eine Uri. Eine URI ist im Prinzip auch ein Verweis auf eine Datei, aber etwas spezieller als nur Datenpfad und Dateiname. <a href=\"https:\/\/de.wikipedia.org\/wiki\/Uniform_Resource_Identifier\" target=\"_blank\" rel=\"noopener\">Info zur URI hier<\/a>! F\u00fcr mich bedeutet dass, ich muss eine Speichermethode doSaveData2Uri so bauen, dass ich \u00fcber eine URI speichern kann. Als Parameter gebe ich der Methode das Element EditText und und die Uri mit, wo die Dateien hin sollen.<\/p>\n<p>Fehlt nur noch die Methode zum Speichern einer Datei, die \u00fcber eine URI definiert wird.<\/p>\n<pre><em>\/\/----------------------------------------------------------------------\r\n<\/em> private void doSaveData2Uri (EditText etQuelle, Uri uri) { \r\n<em>  \/\/Save Date von EditText in Datei via URI\r\n\r\n<\/em>  String myData = etQuelle.getText().toString(); \r\n\r\n  try { \r\n    OutputStream stream = getContentResolver().openOutputStream(uri, \"wt\"); \r\n    PrintWriter writer = new PrintWriter(stream); \r\n    writer.write(myData); \r\n    writer.flush(); \r\n    stream.close(); \r\n    Toast.<em>makeText<\/em>(this, \"Datei wurde gespeichert!\", Toast.<em>LENGTH_LONG<\/em>).show(); \r\n  } catch (Exception e) { \r\n    Log.<em>e<\/em>(getLocalClassName(), \"caught IOException\", e); \r\n    Log.<em>d <\/em>(\"ERROR\", e.toString()); \r\n  } \r\n}<\/pre>\n<p>F\u00fcr das Speichern definiere ich mir mittels getCotentResolver einen Output Stream mit der angegebenen Uri und dem Paramter \u201ewt\u201c. Das wt ist wichtig. W\u00fcrde ich den Paramter weglassen, werden neue Daten zwar in die Datei gespeichert, wenn die Datei aber vorher l\u00e4nger war, bleibt der alte Dateninhalt noch erhalten. Mittels t wird ein truncate erzwungen, d.h. die Datei wird nach dem Speichern der Informationen abgeschnitten, so dass keine alten Daten mehr enthalten sind.<\/p>\n<p>Der eigentliche Speichervorgang findet dann mit writer.write statt. Hier wird der Inhalt des Elements EditText einfach als Stringwert gespeichert und die Datei geschlossen. Am Schluss gebe ich mir mittels Toast Message noch eine kleine Meldung aus, dass der Speichervorgang vollzogen wurde.<\/p>\n<h4><a id=\"post-2963-__RefHeading___Toc1933_247567268\"><\/a>Button Notiz laden<\/h4>\n<p>Der Lade Button l\u00e4uft fast \u00e4hnlich wie der Speicher Button \u2013 nur anders herum! Wieder wird der switch in der onClick Methode erweitert. Dieses Mal rufe ich ein Intent zum \u00d6ffnen und Lesen einer TEXT Datei auf und verwenden den Requestcode LOAD.<\/p>\n<pre><em>\/\/----------------------------------------------------------------------\r\n<\/em> @Override \r\npublic void onClick(View v) { \r\n<em>  \/\/Unser Eingabe Listener\r\n\r\n<\/em>  Intent intent = new Intent(); \r\n\r\n  switch (v.getId()) { \r\n  ... \r\n    case R.id.<em>buttonLaden<\/em>: \r\n      intent = new Intent (Intent.<em>ACTION_OPEN_DOCUMENT<\/em>); \r\n      intent.addCategory(Intent.<em>CATEGORY_DEFAULT<\/em>); \r\n      intent.setType(\"text\/plain\"); \r\n      intent.putExtra(Intent.<em>EXTRA_TITLE<\/em>, \"\"); \r\n      startActivityForResult(intent, <em>REQUESTCODE_LOAD<\/em>); \r\n      break; \r\n  } \r\n}<\/pre>\n<p>Analog dazu in der Methode onActivityResult die Abfrage ob das Intent zum \u00d6ffnen und Lader einer Text Datei ausgew\u00e4hlt wurde und ich bastele mir eine Methode, bei der eine Datei unter Angabe einer URI geladen wird:<\/p>\n<pre><em>\/\/----------------------------------------------------------------------\r\n<\/em> public void onActivityResult(int requestCode, int resultCode, Intent resultData) { \r\n<em>  \/\/Intent auswerten\r\n<\/em> ... \r\n<em>  \/\/Intent: Datei laden\r\n<\/em>  if (requestCode == <em>REQUESTCODE_LOAD <\/em>&amp;&amp; resultCode == Activity.<em>RESULT_OK<\/em>) { \r\n    Uri uri = null; \r\n    if (resultData != null) { \r\n      uri = resultData.getData(); \r\n      doLoadDataFromUri (uri, <em>etEingabe1<\/em>); \r\n    } \r\n  } \r\n}<\/pre>\n<p>Auch hier nehme ich wieder eine eigene Methode, die ich doLoadDataFromUri nenne. Als Parameter gebe ich die die vom Benutzer ausgew\u00e4hlte Uri und das EditText Elemtn mit, in dem die Daten angezeigt werden sollen. Die Lademethode dann im Detail:<\/p>\n<pre><em>\/\/----------------------------------------------------------------------\r\n<\/em> private void doLoadDataFromUri (Uri myUri, EditText etZiel) { \r\n<em>\/\/Text Datei laden von URI. Dann in EditText anzeigen\r\n\r\n<\/em>try { \r\n  InputStreamReader inputStreamReader = new InputStreamReader(getContentResolver().openInputStream(myUri)); \r\n  BufferedReader bufferedReader = new BufferedReader(inputStreamReader); \r\n  StringBuilder sb = new StringBuilder(); \r\n  String s = \"\"; \r\n\r\n<em>  \/\/Datei lesen, String aufbauen\r\n<\/em>  while ((s = bufferedReader.readLine()) != null) { \r\n    Log.<em>d <\/em>(\"Akt gelesen\", s); \r\n    sb.append(s); \r\n  } \r\n\r\n<em>  \/\/Wenn fertig mit lesen: String nach EditText anzeigen\r\n<\/em>  String fileContent = sb.toString(); \r\n  bufferedReader.close(); \r\n  etZiel.setText(fileContent); \r\n\r\n  } catch (IOException e) { \r\n<em>    \/\/Fehlerbehandlung bei Ausnahme\r\n<\/em>    Log.<em>e<\/em>(getLocalClassName(), \"caught IOException\", e); \r\n  } \r\n}<\/pre>\n<p>Dieses Mal hole ich mir einen Stream Reader mit dem ich die Daten lese, mir mittels String Builder einen gro\u00dfen String aufbauen und diesen am Schluss in die als Parameter \u00fcbergebene EditText anzeige.<\/p>\n<h4><a id=\"post-2963-__RefHeading___Toc1935_247567268\"><\/a>Button Vorlesen<\/h4>\n<p>Das machen wir uns einfach und verwenden die Text To Speach Routinen, die uns Google anbietet. Eine private Variable TextToSpeech deklairieren und in der onInit Funktion auf das Textfenster setzen. Dazu kann ich noch den Dialekt spezfizieren \u2013 ich h\u00e4tte es gerne halbwegs verst\u00e4ndlich \u2013 also Deutsch \/ Deutsch! Im onClick Listener aktivere ich dann die Vorlesefunktion und fertig. Sieht dann konkret so aus:<\/p>\n<pre><em>\/\/----------------------------------------------------------------------\r\n<\/em> public class MainActivity extends AppCompatActivity implements View.OnClickListener, TextToSpeech.OnInitListener { \r\n\r\n...\r\n\r\nprivate TextToSpeech tts;\r\n\r\n\u2026.\r\n\r\n<em>\/\/----------------------------------------------------------------------\r\n<\/em> @Override \r\npublic void onClick(View v) { \r\n<em>  \/\/Unser Eingabe Listener\r\n\u2026.<\/em> \r\n  switch (v.getId()) { \r\n\u2026. \r\n    case R.id.<em>buttonVorlesen<\/em>: \r\n      tts = new TextToSpeech(this, this); \r\n      tts.shutdown(); \r\n      break; \r\n  } \r\n}\r\n\r\n\u2026.\r\n\r\n<em>\/\/----------------------------------------------------------------------\r\n<\/em> @Override \r\npublic void onInit(int i) { \r\n  String S1 = <em>etEingabe1<\/em>.getText().toString(); \r\n  tts.setLanguage(Locale.<em>GERMAN<\/em>); \r\n  tts.speak(S1, TextToSpeech.<em>QUEUE_FLUSH<\/em>, null); \r\n}<\/pre>\n<p>Das war es schon.<\/p>\n<h4><a id=\"post-2963-__RefHeading___Toc4744_3831981344\"><\/a>Java Code komplett<\/h4>\n<p>Hinweis: Leider ist die Formatierung etwas besch\u00e4digt. Die Klammern und Einr\u00fcckungen sind umgefallen!<\/p>\n<pre><em>\/\/----------------------------------------------------------------------\r\n<\/em> public class MainActivity extends AppCompatActivity implements View.OnClickListener, TextToSpeech.OnInitListener { \r\n\r\nprivate static EditText <em>etEingabe1<\/em>; \r\n\r\nprivate static final int <em>REQUESTCODE_SAVE <\/em>= 123; <em>\/\/Requestcode f. Export \/ Save\r\n<\/em>private static final int <em>REQUESTCODE_LOAD <\/em>= 125; <em>\/\/Requestcode f. Import \/ Laden\r\n<\/em>private static final int <em>REQUESTCODE_CAMERA <\/em>= 160; <em>\/\/Requestcode f. Camera \/ Scanner\r\n\r\n<\/em>private TextToSpeech tts; \r\n\r\n<em>\/\/----------------------------------------------------------------------\r\n<\/em>@Override \r\nprotected void onCreate(Bundle savedInstanceState) { \r\n  super.onCreate(savedInstanceState); \r\n  setContentView(R.layout.<em>activity_main<\/em>); \r\n\r\n<em>  \/\/EditText Variable init - weil wir sie ueberall nutzen\r\n  etEingabe1 <\/em>= findViewById(R.id.<em>editTextText<\/em>); \r\n\r\n<em>  \/\/Listener fuer Buttons registrieren\r\n<\/em>  findViewById(R.id.<em>imageButton_clrText<\/em>).setOnClickListener(this); \r\n  findViewById(R.id.<em>buttonLaden<\/em>).setOnClickListener(this); \r\n  findViewById(R.id.<em>buttonSpeichern<\/em>).setOnClickListener(this); \r\n  findViewById(R.id.<em>buttonVorlesen<\/em>).setOnClickListener(this); \r\n\r\n<em>  \/\/am Start: Cursor in das Textfeld setzen\r\n  etEingabe1<\/em>.requestFocus(); \r\n} \r\n\r\n<em>\/\/----------------------------------------------------------------------\r\n<\/em>@Override \r\npublic void onClick(View v) { \r\n<em>  \/\/Unser Eingabe Listener\r\n\r\n<\/em>  Intent intent = new Intent(); \r\n\r\n  switch (v.getId()) { \r\n    case R.id.<em>imageButton_clrText<\/em>: \r\n<em>      \/\/Button CLEAR\r\n      etEingabe1<\/em>.setText(\"\"); \r\n<em>      etEingabe1<\/em>.requestFocus(); \r\n      break; \r\n\r\n    case R.id.<em>buttonSpeichern<\/em>: \r\n      intent = new Intent(Intent.<em>ACTION_CREATE_DOCUMENT<\/em>); \r\n      intent.addCategory(Intent.<em>CATEGORY_DEFAULT<\/em>); \r\n      intent.setType(\"text\/plain\"); \r\n      intent.putExtra(Intent.<em>EXTRA_TITLE<\/em>, \"\"); \r\n      startActivityForResult(intent, <em>REQUESTCODE_SAVE<\/em>); \r\n      break; \r\n\r\n    case R.id.<em>buttonLaden<\/em>: \r\n      intent = new Intent (Intent.<em>ACTION_OPEN_DOCUMENT<\/em>); \r\n      intent.addCategory(Intent.<em>CATEGORY_DEFAULT<\/em>); \r\n      intent.setType(\"text\/plain\"); \r\n      intent.putExtra(Intent.<em>EXTRA_TITLE<\/em>, \"\"); \r\n      startActivityForResult(intent, <em>REQUESTCODE_LOAD<\/em>); \r\n      break; \r\n\r\n    case R.id.<em>buttonVorlesen<\/em>: \r\n      tts = new TextToSpeech(this, this); \r\n      tts.shutdown(); \r\n      break; \r\n  } \r\n} \r\n\r\n<em>\/\/----------------------------------------------------------------------\r\n<\/em>@Override \r\npublic void onInit(int i) { \r\n  String S1 = <em>etEingabe1<\/em>.getText().toString(); \r\n  tts.setLanguage(Locale.<em>GERMAN<\/em>); \r\n  tts.speak(S1, TextToSpeech.<em>QUEUE_FLUSH<\/em>, null); \r\n} \r\n\r\n<em>\/\/----------------------------------------------------------------------\r\n<\/em>public void onActivityResult(int requestCode, int resultCode, Intent resultData) { \r\n<em>  \/\/Intent auswerten\r\n\r\n<\/em>  super.onActivityResult(requestCode, resultCode, resultData); \r\n\r\n<em>  \/\/Intent: Datei speichern\r\n<\/em>  if (requestCode == <em>REQUESTCODE_SAVE <\/em>&amp;&amp; resultCode == Activity.<em>RESULT_OK<\/em>) { \r\n    Uri uri = null; \r\n    if (resultData != null) { \r\n      uri = resultData.getData(); \r\n      doSaveData2Uri (<em>etEingabe1<\/em>, uri); \r\n    } \r\n  } \r\n\r\n<em>  \/\/Intent: Datei laden\r\n<\/em>  if (requestCode == <em>REQUESTCODE_LOAD <\/em>&amp;&amp; resultCode == Activity.<em>RESULT_OK<\/em>) { \r\n    Uri uri = null; \r\n    if (resultData != null) { \r\n      uri = resultData.getData(); \r\n      doLoadDataFromUri (uri, <em>etEingabe1<\/em>); \r\n    } \r\n  } \r\n\r\n<em>  \/\/Andere RequestCodes hier einbauen\r\n<\/em>} \r\n\r\n<em>\/\/----------------------------------------------------------------------\r\n<\/em>private void doSaveData2Uri (EditText etQuelle, Uri uri) { \r\n<em>  \/\/Save Date von EditText in Datei via URI\r\n\r\n<\/em>  String myData = etQuelle.getText().toString(); \r\n\r\n  try { \r\n    OutputStream stream = getContentResolver().openOutputStream(uri, \"wt\"); \r\n    PrintWriter writer = new PrintWriter(stream); \r\n    writer.write(myData); \r\n    writer.flush(); \r\n    stream.close(); \r\n    Toast.<em>makeText<\/em>(this, \"Datei wurde gespeichert!\", Toast.<em>LENGTH_LONG<\/em>).show();\r\n  } catch (Exception e) { \r\n    Log.<em>e<\/em>(getLocalClassName(), \"caught IOException\", e); \r\n    Log.<em>d <\/em>(\"ERROR\", e.toString()); \r\n  } \r\n} \r\n\r\n<em>\/\/----------------------------------------------------------------------\r\n<\/em>private void doLoadDataFromUri (Uri myUri, EditText etZiel) { \r\n<em>  \/\/Text Datei laden von URI. Dann in EditText anzeigen\r\n\r\n<\/em>  try { \r\n    InputStreamReader inputStreamReader = new InputStreamReader(getContentResolver().openInputStream(myUri)); \r\n    BufferedReader bufferedReader = new BufferedReader(inputStreamReader); \r\n    StringBuilder sb = new StringBuilder(); \r\n    String s = \"\"; \r\n\r\n<em>    \/\/Datei lesen, String aufbauen\r\n<\/em>    while ((s = bufferedReader.readLine()) != null) { \r\n      Log.<em>d <\/em>(\"Akt gelesen\", s); \r\n      sb.append(s); \r\n    } \r\n\r\n<em>    \/\/Wenn fertig mit lesen: String nach EditText anzeigen\r\n<\/em>    String fileContent = sb.toString(); \r\n    bufferedReader.close(); \r\n    etZiel.setText(fileContent); \r\n\r\n  } catch (IOException e) { \r\n<em>    \/\/Fehlerbehandlung bei Ausnahme\r\n<\/em>    Log.<em>e<\/em>(getLocalClassName(), \"caught IOException\", e); \r\n  } \r\n} \r\n\r\n} <em>\/\/end class<\/em><\/pre>\n<h3><a id=\"post-2963-__RefHeading___Toc2475_2990019160\"><\/a>Der Testlauf<\/h3>\n<p>In den vorherigen Teilen habe ich es ausf\u00fchrlich behandelt. Um die App nun im Emulator zu starten: Device Emulator mit einem ausgew\u00e4hlten Ger\u00e4t starten, App starten. Das Ergebnis sieht dann erwartungsgem\u00e4\u00df so aus:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"393\" height=\"800\" class=\"wp-image-2967\" src=\"https:\/\/www.art-events.de\/weblog\/wp-content\/uploads\/2023\/11\/word-image-2963-4.png\" srcset=\"https:\/\/www.art-events.de\/weblog\/wp-content\/uploads\/2023\/11\/word-image-2963-4.png 393w, https:\/\/www.art-events.de\/weblog\/wp-content\/uploads\/2023\/11\/word-image-2963-4-147x300.png 147w\" sizes=\"auto, (max-width: 393px) 100vw, 393px\" \/><\/p>\n<p>Spielen wir mit allen Funktionen herum, um uns einen Eindruck von der kleinen App zu verschaffen. Wir k\u00f6nnen Texte tippen, speichern, laden und vorlese lassen. Tipp: wenn ihr beim Vorlesen nichts h\u00f6rt \u2013 Lautst\u00e4rke im Device Emulator hochdrehen. Gibt extra Buttons f\u00fcr die Lautst\u00e4rke auf dem Smartphone!<\/p>\n<h3><a id=\"post-2963-__RefHeading___Toc2477_2990019160\"><\/a>Fehlersuche \/ Optimierung<\/h3>\n<p>Programmierung ist Handwerk. Viele verschiedene Wege f\u00fchren zum Ergebnis und manchmal geschehen unerwartete Sachen. Daher m\u00fcssen wir Programmierer unser Werk testen. M\u00f6glichst bevor Kunden oder echte Einsatzf\u00e4lle es tun.<\/p>\n<p>Code abzutippen ist heute einfach geworden. Einfach aus dem Internet ein paar Codeschnipsel kopieren, fertig ist ein Programm. Doch wie gut ein Programm ist, entscheidet der Alltag. Ich verbringe z.B. genauso viel Zeit mit dem Testen der Software wie mit dem Programmieren. Und trotzdem passieren im Alltag Sachen mit denen ich nicht rechnete.<\/p>\n<p>Im Fall unserer Anwendung stellt man recht schnell fest, dass etwas nur suboptimal l\u00e4uft. Wird Beispielsweise der Text unten gespeichert und anschlie\u00dfend wieder geladen, sieht er ver\u00e4ndert aus.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"382\" height=\"242\" class=\"wp-image-2968\" src=\"https:\/\/www.art-events.de\/weblog\/wp-content\/uploads\/2023\/11\/word-image-2963-5.png\" srcset=\"https:\/\/www.art-events.de\/weblog\/wp-content\/uploads\/2023\/11\/word-image-2963-5.png 382w, https:\/\/www.art-events.de\/weblog\/wp-content\/uploads\/2023\/11\/word-image-2963-5-300x190.png 300w\" sizes=\"auto, (max-width: 382px) 100vw, 382px\" \/><\/p>\n<p>Wenn ich das jetzt speichere und mir im Device Explorer die Datei ansehe sieht sie so aus:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"524\" height=\"135\" class=\"wp-image-2969\" src=\"https:\/\/www.art-events.de\/weblog\/wp-content\/uploads\/2023\/11\/word-image-2963-6.png\" srcset=\"https:\/\/www.art-events.de\/weblog\/wp-content\/uploads\/2023\/11\/word-image-2963-6.png 524w, https:\/\/www.art-events.de\/weblog\/wp-content\/uploads\/2023\/11\/word-image-2963-6-300x77.png 300w\" sizes=\"auto, (max-width: 524px) 100vw, 524px\" \/><\/p>\n<p>Wenn ich die Datei aber dann wieder lade schaut sie so aus.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"384\" height=\"232\" class=\"wp-image-2970\" src=\"https:\/\/www.art-events.de\/weblog\/wp-content\/uploads\/2023\/11\/word-image-2963-7.png\" srcset=\"https:\/\/www.art-events.de\/weblog\/wp-content\/uploads\/2023\/11\/word-image-2963-7.png 384w, https:\/\/www.art-events.de\/weblog\/wp-content\/uploads\/2023\/11\/word-image-2963-7-300x181.png 300w\" sizes=\"auto, (max-width: 384px) 100vw, 384px\" \/><\/p>\n<p>Offensichtlich hat sie ihre Zeilenumbr\u00fcche verloren und alles wird einfach hintereinander angezeigt. Um zu begreifen, was passiert ist, mache ich folgendes:<\/p>\n<p>Ich gebe den Text neu ein und starte den Device Explorer. Dort navigiere ich in das Verzeichnis \/Download und \u00fcbertragen die Datei an meinen PC. Auf dem PC schaue ich sie mir in einem Editor in verschiedenen Darstellungen, zuerst in der ASCII Text und anschlie\u00dfend in der HEX Darstellung.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"701\" height=\"370\" class=\"wp-image-2971\" src=\"https:\/\/www.art-events.de\/weblog\/wp-content\/uploads\/2023\/11\/word-image-2963-8.png\" srcset=\"https:\/\/www.art-events.de\/weblog\/wp-content\/uploads\/2023\/11\/word-image-2963-8.png 701w, https:\/\/www.art-events.de\/weblog\/wp-content\/uploads\/2023\/11\/word-image-2963-8-300x158.png 300w\" sizes=\"auto, (max-width: 701px) 100vw, 701px\" \/><\/p>\n<p>Anschlie\u00dfend wiederhole ich den Vorgang mit der Datei, die nach dem Laden keine Zeilenumbr\u00fcche mehr hat.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"656\" height=\"294\" class=\"wp-image-2972\" src=\"https:\/\/www.art-events.de\/weblog\/wp-content\/uploads\/2023\/11\/word-image-2963-9.png\" srcset=\"https:\/\/www.art-events.de\/weblog\/wp-content\/uploads\/2023\/11\/word-image-2963-9.png 656w, https:\/\/www.art-events.de\/weblog\/wp-content\/uploads\/2023\/11\/word-image-2963-9-300x134.png 300w\" sizes=\"auto, (max-width: 656px) 100vw, 656px\" \/><\/p>\n<p>Beim Vergleichen der beiden Anzeigen vorher \/ nachher f\u00e4llt relativ schnell auf: Der Zeilenumbruch nach dem Punkt (Hex 2E) wird als LF (Line Feed) mit Hex 0A gespeichert. Nach dem Laden ist das LF jedoch nicht mehr da und man kann in der Hex Darstellung nur noch den Punkt 2E Hex finden. (Das war \u00fcbrigens der Grund, warum ich einen Punkt als Satzende eingeben, bevor ich die ENTER Taste dr\u00fcckte. So lassen sich die Daten in der Hexdarstellung leichter finden. Man muss nur den Punkt suchen!)<\/p>\n<p>Problem also: meine Laderoutine doLoadDataFromUri filtert irgendwie den Zeilenumbruch heraus und muss optimiert werden, damit Zeilenumbr\u00fcche richtig dargestellt werden.<\/p>\n<p>Bisher wurden die Daten satzweise gelesen und an einen Stringbuffer angeh\u00e4ngt:<\/p>\n<pre><em>\/\/Datei lesen, String aufbauen\r\n<\/em>  while ((s = bufferedReader.readLine()) != null) { \r\n    Log.<em>d <\/em>(\"Akt gelesen\", s); \r\n    sb.append(s); \r\n  }<\/pre>\n<p>Bei dieser Logik scheint nun aber das LF 0A Hex verloren zu gehen. Ich habe jetzt folgende M\u00f6glichkeiten:<\/p>\n<p>1) Ich kann jetzt die Dokumentationen von Google oder Java zu Rate ziehen und versuchen herauszufinden, ob es eine L\u00f6sung gibt, wie auch das Stringende 0A Hex in den Ziel-String \u00fcbernommen werden kann<\/p>\n<p>oder<\/p>\n<p>2) ich f\u00fcge einfach selbst CF LF (Hex 0D 0A) am jeweiligen Stringende ein. Theoretisch reicht LF Hex 0A, aber es gibt Editoren, die erwarten CR LF als Zeilenende. Wenn ich also beides einf\u00fcge, bin ich auf der garantiert sicheren Seite und muss mir keine Gedanken machen, wenn der Benutzer die Datei wo-auch-immer noch verarbeiten will.<\/p>\n<p>Aufgrund der einfachen L\u00f6sung w\u00e4hle ich L\u00f6sung 2 und bastele einfach die Schreibroutine etwas um:<\/p>\n<pre><em>\/\/Datei lesen, String aufbauen\r\n<\/em>while ((s = bufferedReader.readLine()) != null) { \r\n  Log.<em>d <\/em>(\"Akt gelesen\", s);\r\n  s = s + \"\\r\\n\"; \r\n  sb.append(s) ; \r\n}<\/pre>\n<p>In der Praxis w\u00fcrde ich es allerdings so darstellen:<\/p>\n<pre><em>\/\/Datei lesen, String aufbauen\r\n<\/em>while ((s = bufferedReader.readLine()) != null) { \r\n  Log.<em>d <\/em>(\"Akt gelesen\", s); \r\n  sb.append(s + \"\\r\\n\"); \r\n}<\/pre>\n<p>Anschlie\u00dfend nat\u00fcrlich wieder testen. Nun stellt sich heraus: Texte werden mit Umbr\u00fcchen gespeichert und haben keine Ver\u00e4nderung mehr!<\/p>\n<hr \/>\n<p>Das Video zu dieser App ist zweigeteilt:<\/p>\n<p>Teil A: XML Layout<\/p>\n<p><iframe loading=\"lazy\" title=\"Programmierung einer Notizen App mit dem Android Studio. Teil A: die XML Layout Datei\" width=\"640\" height=\"360\" src=\"https:\/\/www.youtube.com\/embed\/EUEZJ5omm7w?feature=oembed\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><\/p>\n<p>Teil B: Der Java Code<\/p>\n<p><iframe loading=\"lazy\" title=\"Programmierung einer Notizen App mit dem Android Studio. Teil B: der Java Code\" width=\"640\" height=\"360\" src=\"https:\/\/www.youtube.com\/embed\/WYsAYs-vdjs?feature=oembed\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><\/p>\n<hr \/>\n<p><a href=\"https:\/\/www.art-events.de\/weblog\/wp-content\/uploads\/2021\/06\/20080607hjwx-204.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-2104\" src=\"https:\/\/www.art-events.de\/weblog\/wp-content\/uploads\/2021\/06\/20080607hjwx-204.jpg\" alt=\"\" width=\"204\" height=\"153\" \/><\/a><\/p>\n<p>Text und Entwurf. (c)\u00a0<a href=\"https:\/\/www.terminal-systems.de\/\" target=\"_blank\" rel=\"noopener\">AE SYSTEME Testcenter<\/a>, Hans-J. Walter<br \/>\nHans-J. Walter ist Programmierer f\u00fcr Windows DOT.NET \/ C# und Android und als eingetragener, unabh\u00e4ngiger Journalist verantwortlich f\u00fcr Fachberichte und Schulungstexte \u00fcber Technik u. Entwicklung.\u00a0<a href=\"mailto:hjw@terminal-systems.de\">hjw@terminal-systems.de<\/a><\/p>\n<p><em>F\u00fcr diese und alle nachfolgenden Seiten gilt ebenso der obligatorische Hinweis: Alle Angaben ohne Gew\u00e4hr. Bilder und Codes zeigen Beispiele. Diese Beschreibung bezieht sich auf unsere Installation und stellt keine Bewertung der verwendeten Techniken da. Fehler und Irrt\u00fcmer vorbehalten!<\/em><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Unser AE Android Kochbuch Teil 3A: Notizen App Reloaded. Schreiben. Speichern. Laden. Vorlesen lassen. In Teil 3 pr\u00e4sentierte ich eine kleine Notizbuch App, die aber nur eingeschr\u00e4nkt nutzbar war. Daher gibt es hier Teil 3A &#8211; die Notizen App Reloaded! In diesem Teil widme ich mich dem gleichen Thema, aber nun basteln wir die App [&hellip;]<\/p>\n","protected":false},"author":6,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[33,34],"tags":[],"class_list":["post-2963","post","type-post","status-publish","format-standard","hentry","category-android","category-programmierung","entry"],"_links":{"self":[{"href":"https:\/\/www.art-events.de\/weblog\/wp-json\/wp\/v2\/posts\/2963","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.art-events.de\/weblog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.art-events.de\/weblog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.art-events.de\/weblog\/wp-json\/wp\/v2\/users\/6"}],"replies":[{"embeddable":true,"href":"https:\/\/www.art-events.de\/weblog\/wp-json\/wp\/v2\/comments?post=2963"}],"version-history":[{"count":0,"href":"https:\/\/www.art-events.de\/weblog\/wp-json\/wp\/v2\/posts\/2963\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.art-events.de\/weblog\/wp-json\/wp\/v2\/media?parent=2963"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.art-events.de\/weblog\/wp-json\/wp\/v2\/categories?post=2963"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.art-events.de\/weblog\/wp-json\/wp\/v2\/tags?post=2963"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}