Beispiel: App die mittels PHP und JSON mit einer Online DB kommuniziert

  • 24 Antworten
  • Letztes Antwortdatum
kosmus

kosmus

Erfahrenes Mitglied
62
Da es in letzter Zeit häufig Fragen zum Thema Zugriff auf Daten im Internet gab, habe ich mal eine kleine App erstellt um ein paar Basics zu vermitteln.

Der Quellcode kann nach Belieben weiterverwendet werden, die App ist jedoch nur für das Tutorial entwickelt und aus verschiedenen Gründen nicht für den Echtbetrieb geeignet.

Wie die Überschrift schon vermuten lässt werde ich hier vier Sachen zeigen:
- Zugriff auf eine MySQL Datenbank mittels PHP (Select, Insert, Update und Delete).
- HTTP Verbindung in einem ASync Task
- Verarbeiten einer Antwort im JSON Format
- Darstellung in einer ListView mit eigenem Adapter

Ich unterstelle dabei, dass ihr Grundkenntnisse in SQL und Java habt.

Die App greift auf eine in einer MySQL DB abgelegten To Do Liste zu. Sie kann alle Einträge anzeigen, neue Einträge anlegen, Einträge ändern und Einträge löschen. Eine Vorschau wie die App mit dem ListView aussieht habe ich mal angehängt.

Die Serverseite:
Ihr braucht einen Server (bzw. Platz auf einem Server) der PHP verarbeiten kann und eine MySQL Datenbank. Zum Entwickeln und Testen könnt ihr XAMPP benutzen (einfach mal Google fragen).

Die App erwartet eine Tabelle mit zwei Spalten (ID und Eintrag):

Code:
CREATE TABLE IF NOT EXISTS `ToDoListe` (
  `ID` int(4) NOT NULL,
  `Eintrag` varchar(32) NOT NULL
);

Zoopa hat mir noch den Tipp gegeben, dass ihr für ID noch AUTO_INCREMENT mitgeben könnt, dann könnt ihr euch im Insert Skript (siehe unten) auch das ermitteln der nächsten freien ID sparen

Als nächsten braucht man ein paar PHP Scripte. Die Ausgaben der Skripte sind im JSON Format und können später von der App gelesen werden. Jede Antwort in meinen PHP Skripten hat ein JSON Array mit Namen Status, dieses besteht aus einer Zahl und einem String.
-1 steht für Fehlermeldungen,
0 für einen erfolgreichen Select Befehl
1 für einen erfolgreichen Insert Befehl
2 für einen erfolgreichen Update Befehl
3 für einen erfolgreichen Delete Befehl
So kann die App dann zur Laufzeit entscheiden, wie sie die Antwort weiterverarbeiten soll. Diese Werte entsprechen keinem mir bekannten Standard, je nachdem wie ihr die Weiterverarbeitung gestaltet braucht ihr mehr oder weniger Status Meldungen oder könnt sogar ganz darauf verzichten.

Dieses erste Skript stellt eine Verbindung zur Datenbank her:

PHP:
<?php
$verbindung = mysql_connect ("servername",
"user", "passwort")
or die ('{"Status":["-1","Verbindung zum  Server Fehlgeschlagen"]}');

mysql_select_db("meineTestDB")
or die ('{"Status":["-1","Datenbank nicht gefunden"]}');
mysql_query("SET NAMES 'utf8'");

?>

Servername, User und Passwort müssen natürlich entsprechend angepasst werden.

ui_3k1 hat mich darauf hingewiesen, dass es Fehler mit Sonderzeichen geben könnte, deshalb ist es Sinnvoll mit mysql_query("SET NAMES 'utf8'"); die Zeichensatzkodierung einzustellen.

Mit include kann dieses Skript in die anderen eingebunden werden, sodass man nicht immer alles neu doppelt und dreifach schreiben muss.

Das nächste Skript setzt einen Select Befehl ab und gibt die Antwort im JSON Format zurück. Der Antwort enthält neben dem Status Array ein zweites Array mit dem Namen Liste. Hier drin befinden sich alle Einträge aus der To Do Liste.

PHP:
<?php
include("connect.php");

// SQL Query abschicken
$result = mysql_query("SELECT * FROM ToDoListe");
	//Schleife bis alle Eintragungen in Array gespeichert
	$listenArray["Liste"] = array();
	while($row = mysql_fetch_array($result)) {
		$listeneintrag = array();
		$listeneintrag["ID"] = $row["ID"];
		$listeneintrag["Eintrag"] = $row["Eintrag"];
		array_push($listenArray["Liste"], $listeneintrag);
	}
	//Ausgabe im JSON Format
	$listenArray["Status"] = ["0","Select erfolgreich"];
	echo json_encode($listenArray);


//Verbindung trennen.
mysql_close($verbindung);

?>
Achtet darauf, dass die Datenbank Verbindung am Ende auch getrennt wird.
In diesem Skript wird der JSON String mit json_encode() erzeugt, sodass man ihn nicht mühsam von Hand zusammen bauen muss.

Das nächsten drei Skripte sind sehr ähnlich, sie verbinden sich mit der DB setzen eine Query ab und trennen die Verbindung danach wieder. Sie liefern alle eine Antwort mit dem oben erläuterten Status Array.
Die benötigten Variablen werden über HTTP dabei mit der POST Methode übermittelt, man kann natürlich auch GET verwenden denn findet man die Variablen nicht in $_POST[] sondern in $_GET[]. Die Skripten prüfen vor dem Verbindungsaufbau mit der DB ob alle benötigten Variablen auch übermittelt wurden.
Insert für einen neuen Eintrag in der Liste:
PHP:
<?php
//Prüfen ob die über HTTP übermittelten Werte vorhanden sind
if(isset($_POST["Eintrag"])) {
	$Eintrag = $_POST["Eintrag"];
	
	//Mit DB Verbinden
	include("connect.php");

	//Nächste freie ID ermitteln
	$ID = mysql_result(
	mysql_query("SELECT MAX(ID) FROM ToDoListe") ,0);
	$ID++;

	// SQL Query abschicken
	$result = mysql_query("INSERT INTO ToDoListe (ID, Eintrag) VALUES ('$ID','$Eintrag')");

	//Erfolg prüfen
	if ($result) {
		echo '{"Status":["1","Insert erfolgreich"]}';
	} else {
		echo '{"Status":["-1","Insert fehlgeschlagen"]}';
	}

	//Verbindung Trennen
	mysql_close($verbindung);
	
	
}
else {
	//Fehler falls die über HTTP übermittelten Werte nicht vorhanden
	echo '{"Status":["-1","Daten wurden nicht verschickt"]}';
}
?>

Update um einen Eintrag zu bearbeiten:
PHP:
<?php
//Prüfen ob die über HTTP übermittelten Werte vorhanden sind
if(isset($_POST["Eintrag"]) && isset($_POST["ID"])) {
	$Eintrag = $_POST["Eintrag"];
	$ID = $_POST["ID"];
	//Mit DB Verbinden
	include("connect.php");

	// SQL Query abschicken
	$result = mysql_query("UPDATE ToDoListe SET Eintrag = '$Eintrag' WHERE ID = '$ID'");

	//Erfolg prüfen
	if ($result) {
		echo '{"Status":["2","Update erfolgreich"]}';
	} else {
		echo '{"Status":["-1","Update fehlgeschlagen"]}';
	}

	//Verbindung Trennen
	mysql_close($verbindung);
}
else {
	//Fehler falls die über HTTP übermittelten Werte nicht vorhanden
	echo '{"Status":["-1","Daten wurden nicht verschickt"]}';
}
?>

Und Delete zum Löschen eines Eintrags:

PHP:
<?php
//Prüfen ob die über HTTP übermittelten Werte vorhanden sind
if(isset($_POST["ID"])) {
	$ID = $_POST["ID"];
	//Mit DB Verbinden
	include("connect.php");

	// SQL Query abschicken
	$result = mysql_query("DELETE FROM ToDoListe WHERE ID = '$ID'");

	//Erfolg prüfen
	if ($result) {
		echo '{"Status":["3","Delete erfolgreich"]}';
	} else {
		echo '{"Status":["-1","Delete fehlgeschlagen"]}';
	}

	//Verbindung Trennen
	mysql_close($verbindung);
}
else {
	//Fehler falls die über HTTP übermittelten Werte nicht vorhanden
	echo '{"Status":["-1","Daten wurden nicht verschickt"]}';
}
?>

Damit ist die Serverseite fertig.

Die Android App:

Der XML Teil

Ich fange mal mit den XML Dateien an, diese sind sehr einfach:
Eine XML Datei für das Layout der MainActivity. Diese besteht nur aus einem ListView:
Code:
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="kosmus.example.todoliste.MainActivity"
    tools:ignore="MergeRootFrame" />

Dann braucht man noch eine Layout-Datei für einzelnen Listeneinträge. Diese besteht bei mir aus einem TextView und zwei ImageButtons.
Beachtet, dass ich den Button bereits in der XML-Datei onClick Methoden zuweise.
Code:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal" >

    <TextView
        android:id="@+id/eintragTextView"
        android:layout_width="0dip"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:text=""
        android:textSize="20dip" />

    <ImageButton
        android:id="@+id/editButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:contentDescription="bearbeiten"
        android:onClick="doEdit"
        android:src="@android:drawable/ic_menu_edit" />

    <ImageButton
        android:id="@+id/deleteButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:contentDescription="löschen"
        android:onClick="doDelete"
        android:src="@android:drawable/ic_menu_delete" />

</LinearLayout>

Es ist natürlich kein schöner Stil die Größe des Textes und die Beschreibung für ScreenReader direkt in die XML zu schreiben, aber für dieses kleine Tutorial, lass ich es mal so stehen.
In der XML für das Menü habe ich einen Button für die ActionBar zum Erzeugen eines neuen Eintrags in die To Do Liste.
Code:
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    tools:context="kosmus.example.todoliste.MainActivity" >

    <item
        android:id="@+id/hinzufuegenButton"
        android:icon="@android:drawable/ic_menu_add"
        android:showAsAction="always"
        android:title="Neuer Eintrag">
    </item>

</menu>

Im Manifest braucht man noch die Erlaubnis zum Zugriff aus Internet, damit man mit dem Server kommunizieren kann:
Code:
<uses-permission android:name="android.permission.INTERNET" />

Der Java Teil:

Fangen wir mit den leichten Sachen an. Eine Klasse für einen Datenbankeintrag mit Gettern und Settern:

Code:
public class Eintrag {
	private int id;
	private String eintrag;
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String getEintrag() {
		return eintrag;
	}
	public void setEintrag(String eintrag) {
		this.eintrag = eintrag;
	}
	public Eintrag(int id, String eintrag) {
		this.id = id;
		this.eintrag = eintrag;
	}
	
}

Als nächstes kommt der Adapter um die Einträge in einer ListView darzustellen. Mein Adapter erbt vom BaseAdapter und hat als zusätzliches Attribut eine ArrayList mit den Datenbankeinträgen. Man kann alternativ auch einen ArrayAdapter verwenden, doch mir persönlich gefällt es so besser.
Code:
public class EintragAdapter extends BaseAdapter {
	private ArrayList<Eintrag> eintragListe;
	private Context context;

	public EintragAdapter(Context context) {
		super();
		this.eintragListe = new ArrayList<Eintrag>();
		this.context = context;
	}

	public ArrayList<Eintrag> getEintragListe() {
		return eintragListe;
	}

	@Override
	public int getCount() {
		return eintragListe.size();
	}

	@Override
	public Object getItem(int position) {
		return eintragListe.get(position);
	}

	@Override
	public long getItemId(int position) {
		return eintragListe.get(position).getId();
	}

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		View view = convertView;
		if (view == null) {
			LayoutInflater inflater = (LayoutInflater) context
					.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
			view = inflater.inflate(R.layout.eintrag_layout, parent, false);
		}
		TextView textView = (TextView) view.findViewById(R.id.eintragTextView);
		textView.setText(eintragListe.get(position).getEintrag());
		View deleteButton = view.findViewById(R.id.deleteButton);
		deleteButton.setTag(eintragListe.get(position));
		View editButton = view.findViewById(R.id.editButton);
		editButton.setTag(eintragListe.get(position));

		return view;
	}

}
Das Wichtigste findet in der getView Methode statt. Deshalb möchte ich diese kurz erläutern. Sofern von Listeneinträge vorhanden sind, befindet sich im parameter convertView ein View mit dem weiter gearbeitet werden kann. Ist der ListView bisher leer, der convertView null. In diesem Fall muss mit dem LayoutInflator ein neuer View erzeugt werden. Beachtet, dass ich den Button an dieser Stelle mit setTag() den konkreten Eintrag mitgebe. So kann dieser in den onClick Methoden mit getTag() zur Weiterverarbeitung wieder gefunden werden.


Code:
public interface DBResultHandler {
	public void ergebnisVerarbeiten(String ergebnis);
}
Es wird von der MainActivity implementiert(siehe unten);

Mein AsyncTask nimmt als Parameter im Konstruktor jede Klasse die das Interface implementiert an. So kann dann die Methode zur Verarbeitung nach Ablauf des Tasks ausgeführt werden. Als Parameter für die execute() bzw. doInBackground() Methode werden Strings erwartet. Als erster String muss die URL für die HTTP Verbnindung übergeben werden. Danach können beliebig viele POST Variablen übergeben werden, die dann an den Server übermittelt werden. Auch diese sind als String zu übergeben. Dabei ist immer als String erst der variablen Name und dann der Wert zu übergeben. Falls ihr diese Klasse auch weiterverwenden wollt, habe ich das auch nochmal als Java Doc Kommentar in die Klasse geschrieben.
Code:
/**
 * 
 * beim Aufruf von Execute muss als erster String die URL mitgegeben werden. In
 * den Strings danach können bei Bedarf Name und Wert von Variablen mitgegeben
 * werden, die über die POST Methode an den Server übergeben werden sollen.
 * Beispiel für einen Aufruf: 
 * new DBRequestTask(this)).execute(URLString,VarNameString, VarWertString)
 */
public class DBRequestTask extends AsyncTask<String, Void, String> {
	private DBResultHandler handler;

	public DBRequestTask(DBResultHandler handler) {
		super();
		this.handler = handler;
	}

	@Override
	protected String doInBackground(String... params) {
		String ergebnis = "";
		try {
			if (params.length % 2 == 0)
				throw new Exception("Gerade Zahl an params ist nicht logisch");
			URL url = new URL(params[0]);
			HttpURLConnection conn = (HttpURLConnection) url.openConnection();
			conn.setReadTimeout(10000);
			conn.setConnectTimeout(15000);
			conn.setRequestMethod("POST");
			conn.setDoInput(true);
			conn.setDoOutput(true);
			if (params.length > 1) {
				StringBuilder postDaten = new StringBuilder();
				boolean erster = true;
				for (int i = 1; i < params.length; i = i + 2) {
					if (erster) {
						erster = false;
					} else {
						postDaten.append("&");
					}
					postDaten.append(URLEncoder.encode(params[i], "UTF-8"));
					postDaten.append("=");
					postDaten.append(URLEncoder.encode(params[i + 1], "UTF-8"));
				}
				OutputStream os = conn.getOutputStream();
				BufferedWriter writer = new BufferedWriter(
						new OutputStreamWriter(os, "UTF-8"));
				writer.write(postDaten.toString());
				writer.flush();
				writer.close();
				os.close();
			}
			conn.connect();
			StringBuilder antwort = new StringBuilder("");
			InputStream inputStream = conn.getInputStream();
			InputStreamReader reader = new InputStreamReader(inputStream,
					"UTF-8");
			char[] buffer = new char[128];
			while (reader.read(buffer) > 0) {
				antwort.append(buffer);
			}
			inputStream.close();
			ergebnis = antwort.toString();
		} catch (Exception e) {
			ergebnis = "{\"Status\":[\"-1\",\"HTTP Connection Problem\"]}";
		}
		return ergebnis;
	}

	@Override
	protected void onPostExecute(String result) {
		handler.ergebnisVerarbeiten(result);
	}

}

Zum Schluss kommen wir zur MainActivity. Diese benutzt meinen DBRequestTask an verschiedenen Stellen mit verschiedenen URLS und Parametern um die unterschiedlichen PHP Skripte auf meinem Server aufzurufen. Der DBRequestTast ruft dabei jeweils die ergebnisVerarbeiten verarbeiten Methode meiner MainActivity auf und übergibt als String was vom Server als Antwort zurückgekommen ist. Dieser String wird dann in ein JSON Objekt gepackt, damit er leichter verarbeitet werden kann. Zunächst wird dann aus dem JSON Objekt das Status Array geholt um zu entscheiden, wie die Serverantwort verarbeitet werden muss. Beim Status 0 (erfolgreicher Select Befehl) wird dann das Liste Array verarbeitet. Für jeden Listeneintrag wird ein Eintrag Objekt erzeugt und Eintragliste im eintragAdapter gehangen. Zum Schluss wird die notifyDataSetChanged() Methode des Adapters aufgerufen, damit der ListView seine Anzeige aktualisiert.
Code:
package kosmus.example.todoliste;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.Toast;

public class MainActivity extends Activity implements DBResultHandler {
	private EintragAdapter eintragAdapter;
	private EditText eingabefeld;
	private Eintrag eintrag;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		ListView listView = (ListView) findViewById(R.id.container);
		eintragAdapter = new EintragAdapter(this);
		listView.setAdapter(eintragAdapter);
		ladeDaten();
	}

	private void ladeDaten() {
		(new DBRequestTask(this)).execute(getString(R.string.basis_url)
				+ "selectall.php");
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {

		// Inflate the menu; this adds items to the action bar if it is present.
		getMenuInflater().inflate(R.menu.main, menu);
		return true;
	}

	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		// Handle action bar item clicks here. The action bar will
		// automatically handle clicks on the Home/Up button, so long
		// as you specify a parent activity in AndroidManifest.xml.
		int id = item.getItemId();
		if (id == R.id.hinzufuegenButton) {
			neuerEintrag();
			return true;
		}
		return super.onOptionsItemSelected(item);
	}

	private void neuerEintrag() {
		AlertDialog.Builder alertBuilder = new AlertDialog.Builder(this);
		alertBuilder.setTitle("Neuer Eintrag");
		eingabefeld = new EditText(this);
		alertBuilder.setView(eingabefeld);
		alertBuilder.setCancelable(true);
		alertBuilder.setNegativeButton("Abbrechen",
				new DialogInterface.OnClickListener() {

					@Override
					public void onClick(DialogInterface dialog, int which) {
						dialog.cancel();

					}
				});
		alertBuilder.setPositiveButton("OK",
				new DialogInterface.OnClickListener() {

					@Override
					public void onClick(DialogInterface dialog, int which) {
						(new DBRequestTask(MainActivity.this)).execute(
								getString(R.string.basis_url) + "insert.php",
								"Eintrag", eingabefeld.getText().toString());
					}
				});

		alertBuilder.create().show();
	}

	public void doEdit(View view) {
		if (view.getTag() instanceof Eintrag) {
			eintrag = (Eintrag) view.getTag();
			AlertDialog.Builder alertBuilder = new AlertDialog.Builder(this);
			alertBuilder.setTitle("Eintrag bearbeiten");
			eingabefeld = new EditText(this);
			eingabefeld.setText(eintrag.getEintrag());
			alertBuilder.setView(eingabefeld);
			alertBuilder.setCancelable(true);
			alertBuilder.setNegativeButton("Abbrechen",
					new DialogInterface.OnClickListener() {

						@Override
						public void onClick(DialogInterface dialog, int which) {
							dialog.cancel();

						}
					});
			alertBuilder.setPositiveButton("OK",
					new DialogInterface.OnClickListener() {

						@Override
						public void onClick(DialogInterface dialog, int which) {
							(new DBRequestTask(MainActivity.this)).execute(
									getString(R.string.basis_url)
											+ "update.php", "Eintrag",
									eingabefeld.getText().toString(), "ID",
									Integer.toString(eintrag.getId()));
						}
					});

			alertBuilder.create().show();
		}
	}

	public void doDelete(View view) {
		if (view.getTag() instanceof Eintrag) {
			eintrag = (Eintrag) view.getTag();
			(new DBRequestTask(MainActivity.this)).execute(
					getString(R.string.basis_url) + "delete.php", "ID",
					Integer.toString(eintrag.getId()));

		}
	}

	@Override
	public void ergebnisVerarbeiten(String ergebnis) {
		try {
			JSONObject jsonErgebnis = new JSONObject(ergebnis);
			JSONArray statusArray = jsonErgebnis.getJSONArray("Status");
			int status = statusArray.getInt(0);
			switch (status) {
			case 0:
				JSONArray datenArray = jsonErgebnis.getJSONArray("Liste");
				eintragAdapter.getEintragListe().clear();
				for (int i = 0; i < datenArray.length(); i++) {
					JSONObject einzelsatz = datenArray.getJSONObject(i);
					eintragAdapter.getEintragListe().add(
							new Eintrag(einzelsatz.getInt("ID"), einzelsatz
									.getString("Eintrag")));
				}
				eintragAdapter.notifyDataSetChanged();
				break;
			case 1:
				Toast.makeText(this, statusArray.getString(1),
						Toast.LENGTH_SHORT).show();
				ladeDaten();
				break;
			case 2:
				Toast.makeText(this, statusArray.getString(1),
						Toast.LENGTH_SHORT).show();
				ladeDaten();
				break;
			case 3:
				Toast.makeText(this, statusArray.getString(1),
						Toast.LENGTH_SHORT).show();
				ladeDaten();
				break;

			default:
				Toast.makeText(this, statusArray.getString(1),
						Toast.LENGTH_SHORT).show();
				break;
			}
		} catch (JSONException e) {
			Toast.makeText(this,
					"Serverantwort konnte nicht verarbeitet werden",
					Toast.LENGTH_SHORT).show();
		}
	}

}

Ich hoffe dieses Beispiel hilft euch bei der Entwicklung eurer Apps weiter. Bitte Beachtet das Strings die Datenbanken übergeben werden, vorher geprüft werden sollten, damit keine sql injection durchgeführt werden kann. Darauf habe ich bei diesem kleinen Beispiel verzichtet.
 

Anhänge

  • Screenshot.png
    Screenshot.png
    11,9 KB · Aufrufe: 1.017
Zuletzt bearbeitet:
  • Danke
Reaktionen: mratix, KIA393, akkulader und 3 andere
Hehe, genau in diesem Moment, in dem ich fertig bin und alles funktioniert, entdecke ich hier das perfekte Tutorial :D

Hut ab, es ist wirklich perfekt! Könntest auf jeden Fall ein Buch schreiben. Hab in den letzten Tagen wirklich extrem umfangreich recherchieren müssen, bis ich was Brauchbares gefunden habe... Deswegen kann ich schon ziemlich sicher sagen, dass dieses HOW-TO von dir eine Klasse für sich ist und so im Netz wohl seines gleichen sucht. :)

Eine kleine Ergänzung hab ich noch, da es bei mir mit den Umlauten und Sonderzeichen Probleme gab.
Wenn man vor dem Aufruf der SQL-Methode(n) (in php) die Anweisung
Code:
mysql_query("SET NAMES 'utf8'");
hinzufügt, ist man in Sachen Encoding auf der sicheren Seite.

An dieser Stelle noch mal ein riesiges "Danke" von mir!
 
Du könntest noch AUTO_INCREMENT für die ID verwenden, dann kann man sich den Aufwand mit dem manuellen zählen der ID sparen.
 
Danke für das feedback, ich werde eure Ideen gleich ergänzen
 
Bei mir meckert er in der select.php, dass in Zeile 15 ein Syntaxfehler sei.
Wenn ich die Zeile auskommentiere, dann meckert er nicht mehr.

Leider kommt beim Starten der App direkt der Fehler, dass die Serverantwort nicht verarbeitet werden konnte.

Weil's ja n SELECT Fehler seien könnte, habe ich in der Main den switch/case geändert. Der Eintrag von 0 liegt jetzt auf default. Der Fehler bleibt aber.



Noch ne Frage: Wo soll der Code fürs Menu rein? In eine Menu-Activity?!



edit: Also wenn ich die Zeile so drin lasse, dann kommt nur n weißes Bild. Keine Fehlermeldung mehr.
Zeile 15 ist:
Code:
    $listenArray["Status"] = ["0","Select erfolgreich"];
 
Zuletzt bearbeitet:
Ich seh gerade gar nicht was du jetzt an der Zeile geändert hast um den syntaxfehler zu beseitigen, kannst du mir da vielleicht die Fehlermeldung geben, damit ich es noch korrigieren kann.

Das weiße Bild ist richtig sofern du noch keine Einträge in der DB hast.

Der code für das Menü... Der XML code gehört in den Ordner /res/menu und der Java code kann so in der MainActivity.java bleiben, wie ich es gepostet habe
 
MySQL ist ja nun schon ganz schön lange auf dem Markt und es wird empfohlen besser MySQLi zu benutzen.

Wie du schon selber sagtest du überprüfst bzw. escapst nicht einmal deine POST Parameter.

Wenn du mit MySQLI Prepared Statments nutzt, hättest du hier schon mal eine Sorge weniger.
 
Ich hab noch nicht so ganz verstanden, wie das Einfügen in die MySQL funktioniert :S

Beim Klicken auf "einfügen", wird ein neues DBRequestTask-Objekt erzeugt.
(Sagen wir mal ich schreib in den Handler:
Code:
 public void ergebnisVerarbeiten(int id, String name, int spiel, int guv, int tore, int diff, int pkt);

Dieses ruft die Methode ergebnisVerarbeiten() der this-Klasse auf.
Aber was muss ich dann in der ergebnisVerarbeiten() machen? Bis jetzt kann die Methode doch nur auslesen oder nicht?

Kann man den DB RequestTask so umbauen, dass er auch int nimmt?
Auch, wenns vll hardcoded ist .. also dass er nur (int, string ,int,int,int,int,int) nimmt.
Ich meine das:
Code:
 	@Override
	public void ergebnisVerarbeiten(String ergebnis) {
		try {
			JSONObject jsonErgebnis = new JSONObject(ergebnis);
			JSONArray statusArray = jsonErgebnis.getJSONArray("Status");
			int status = statusArray.getInt(0);
			System.out.println("Status: " + status);
			switch (status) {
			case 0:
				System.out.println("case 0 MAIN ergebnisVerarbeiten");
				 JSONArray datenArray = jsonErgebnis.getJSONArray("Liste");
				
				eintragAdapter.getMannschaftListe().clear();
				for (int i = 0; i < datenArray.length(); i++) {
					JSONObject einzelsatz = datenArray.getJSONObject(i);
					eintragAdapter.getMannschaftListe().add(
							
							new Mannschaft(einzelsatz.getInt("id"), 
											einzelsatz.getString("name"), 
											einzelsatz.getInt("spiele"),
											
											einzelsatz.getInt("guv"),
											einzelsatz.getInt("tore"),
											einzelsatz.getInt("diff"),
											einzelsatz.getInt("pkt")
											
											
									
									));
					
				}
				eintragAdapter.notifyDataSetChanged();
				 
				break;
 
Zuletzt bearbeitet:
Habe mittlerweile erstmal alle Attribute der Objekte von int auf String geändert und versuche nun folgendes zum Insert aufzurufen:

Code:
(new DBRequestTask(this)).execute(getString(R.string.basis_url) + "insert.php", "name", "hallo","spiele", "10","guv", "20", "tore", "30","diff", "40","pkt" ,"50");

Trotzdem ruft er die Methode auf, die nur einen Parameter braucht, anstatt die, die 12 hat.

Code:
@Override
	public void ergebnisVerarbeiten( String ergebnis )
	{
		System.out.println("falsche Methode");
	}
	
@Override
public void ergebnisVerarbeiten(String name, String nameWert, 
				String spiel, String spielWert,
				String guv, String guvWert, 
				String tore,String toreWert ,
				String diff, String diffWert,
				String pkt, String pktWert)

{System.out.println("richtige Methode");
}



Meine DBResultHandler sieht so aus:
Code:
public interface DBResultHandler {
	public void ergebnisVerarbeiten(String ergebnis);
   public void ergebnisVerarbeiten(String name, String nameWert, 
		   							String spiel, String spielWert,
		   							String guv, String guvWert, 
		   							String tore,String toreWert ,
		   							String diff, String diffWert,
		   							String pkt, String pktWert);

Und meineinsert.php so:
Code:
<?php
//Prüfen ob die über HTTP übermittelten Werte vorhanden sind
if(isset($_POST["Eintrag"])) {
    $Eintrag = $_POST["Eintrag"];
    
    //Mit DB Verbinden
    include("connect.php");

    //Nächste freie ID ermitteln
    $ID = mysql_result(
    mysql_query("SELECT MAX(ID) FROM mannschaften") ,0);
    $ID++;

    // SQL Query abschicken
    $result = mysql_query("INSERT INTO mannschaften (id, name, spiele, guv, tore, diff, pkt) VALUES ('$id','$name','$spiele','$guv','$tore','$diff','$pkt')");

    //Erfolg prüfen
    if ($result) {
        echo '{"Status":["1","Insert erfolgreich"]}';
    } else {
        echo '{"Status":["-1","Insert fehlgeschlagen"]}';
    }

    //Verbindung Trennen
    mysql_close($verbindung);
    
    
}
else {
    //Fehler falls die über HTTP übermittelten Werte nicht vorhanden
    echo '{"Status":["-1","Daten wurden nicht verschickt"]}';
}
?>


Soll ergebnisVerarbeiten dann eigentlich leer bleiben in der aufrufenden Methode?




edit:

Wenn ich debugge, dann kommt er beim Aufruf des 12stelligen DBRequests zu einer ClassNotFoundException, die geworfen wird, sodass keine Fehlermeldung kommt.
Hier:
Code:
 (new DBRequestTask(Admin.this)).execute(getString(R.string.basis_url) + "insert.php", "name", "MannschaftsName","spiele", "10","guv", "20", "tore", "30","diff", "40","pkt" ,"50");


edit2:
Also er ruft den DBRequest auf und führt auch die postDaten.append Methoden richtig aus..
Hab mal ein System.out.println gemacht und es wird alles richtig zurückgegeben.


Trotzdem ruft er am Ende iwie doch wieder die ergebnisVerarbeiten (String ergebnis) auf.
 
Zuletzt bearbeitet:
Der Fehler ist in den ersten zeilen deines PHP Skripts
PHP:
<?php
//Prüfen ob die über HTTP übermittelten Werte vorhanden sind
if(isset($_POST["Eintrag"])) {
 $Eintrag = $_POST["Eintrag"];
hier müsstest du alle Variablen verarbeiten die du über geben hast:
PHP:
if(isset($_POST["name"]) 
&& isset($_POST["spiele"])
&& isset($_POST["guv"])
&& isset($_POST["tore"])
&& isset($_POST["diff"])
&& isset($_POST["pkt"])
)
 {
    $name = $_POST["name"];
    $guv = $_POST["guv"];
    //...
und natürlich meine $_POST["Eintrag"] nicht mehr in der if Abfrage prüfen... Dein Update Skript ist wahrscheinlich ähnlich falsch.

Und wie killphil75 schon richtig festgestellt hat, ist das Skript natürlich nicht sicher genug für einen produktiven Einsatz. Du solltest im PHP Skript prüfen, ob die Werte in den Variablen plausibel sind und keine verbotenen Zeichen (insbesondere ";") enthalten.
 
Zuletzt bearbeitet:
  • Danke
Reaktionen: akkulader
Geil! Danke!!!
Das mit den Sonderzeichen wollte ich eigentlich schon Javaseitig prüfen, da ich, wie du sicher gemerkt hast, keine Ahnung von php hab :D

Danke Dir!
 
Ich hätte noch eine Frage: Ist es mit deinem Beispiel möglich, verschiedene Tabellen der Datenbank in einer Activity anzusprechen?

Beispiel beim Auslesen:
Ich habe die Tabelle Mannschaften und die Tabelle Spieltage

Jetzt habe ich die "selectall.php", in der die Daten für "Mannschaften" gespeichert sind.
Dann habe ich noch die "selectallSpieltage.php", mit der ich die Tabelle "Spieltag" auslesen könnte.


Problem ist ja, dass ich in eine Activity dann erstmal alle Mannschaften laden will also folgendes eingebe:
Code:
	public void ladeDaten() {
		(new DBRequestTask(this)).execute("http://name.bplaced.net/selectall.php");
	}

	@Override
	public void ergebnisVerarbeiten(String ergebnis)
	{
           hier verarbeite ich die daten
        }

Wie kann ich jetzt beispielsweise die "selectallSpieltage.php" ansprechen?
Es wird doch dann wieder die gleiche ergebnisVerarbeiten() aufgerufen oder nicht?

Sorry für die ganzen Fragen:unsure:
 
also du könntest z. B. über den Status gehen.
in meinem Beispiel war -1 Fehler, 0 Select, 1 insert, 2 update, 3 delete.

du könntest z. B. 10 select Mannschaft, 11 insert Mannschaft, 12 update Mannschaft, 13 delete Mannschaft nehmen und dann 20 select Spieltag, 21 insert Spieltag, 22 update Spieltag, 23 delete Spieltag.

mit einem Switch könntest du dann auf die verschiedenen Antworten reagieren.

und noch ein Nachtrag zu deinem Post davor. Es ist zwar gut die Daten schon Client seitig zu prüfen, damit der Benutzer sofort eine Rückmeldung bekommt und nicht unnötig Anfragen an den Server schickt. Aber trotzdem sollte man Server seitig nochmal prüfen, denn ein Hacker wird deine Skripte könnte deine skripte mit seiner eigenen App aufrufen und so deine DB manipulieren. ich werde die Skripte im Anfangspost gleich noch etwas aktualisieren, damit sie wenigstens etwas sicherer sind.

edit:
aus irgendeinem Grund kann ich meinen Eingangspost nicht mehr ändern, deshalb hier die überarbeiteten PHP Skripte:

connect:
PHP:
<?php
//error_reporting(0);
$verbindung = new mysqli("servername",
"user", "passwort", "datenbank");
if ($verbindung->connect_errno) {
	echo '{"Status":["-1","Verbindung zum  Server Fehlgeschlagen"]}';
	exit();
}
$verbindung->set_charset("utf8");

?>

select:
PHP:
<?php
include("connect.php");

// SQL Query abschicken
	$result =  $verbindung->query("SELECT * FROM ToDoListe");
	//Schleife bis alle Eintragungen in Array gespeichert
	$listenArray["Liste"] = array();
	while($row = $result->fetch_array()) {
		$listeneintrag = array();
		$listeneintrag["ID"] = $row["ID"];
		$listeneintrag["Eintrag"] = $row["Eintrag"];
		array_push($listenArray["Liste"], $listeneintrag);
	}
	//Ausgabe im JSON Format
	$listenArray["Status"] = ["0","Select erfolgreich"];
	echo json_encode($listenArray);

$verbindung->close();
?>

insert:
PHP:
<?php
//Prüfen ob die über HTTP übermittelten Werte vorhanden sind
if(isset($_POST["Eintrag"])) {
	
	//Mit DB Verbinden
	include("connect.php");

	//zeichen die Einfluss auf die Query haben mit ändern
	$Eintrag = $verbindung->real_escape_string($_POST["Eintrag"]);


	//Nächste freie ID ermitteln
	$result = $verbindung->query("SELECT MAX(ID) FROM ToDoListe");
	$row = $result->fetch_row();
	$ID = $row[0];
	$ID++;

	// SQL Query vorbereiten
	$prep = $verbindung->prepare("INSERT INTO ToDoListe (ID, Eintrag) VALUES (?,?)");
	
	// Variablen übergeben is = ein Integer und ein String
	if(!$prep->bind_param("is", $ID, $Eintrag)) {
		echo '{"Status":["-1","Daten entsprechen nicht dem erwarteten Format"]}';
		$verbindung->close();
		exit();
	}

	// Query abschicken und erfolg prüfen
	if($prep->execute()) {
		echo '{"Status":["1","Daten gespeichert"]}';
	} else {
		echo '{"Status":["-1","Fehler beim schreiben in die Datenbank"]}';
	}
	$verbindung->close();
}
else {
	//Fehler falls die über HTTP übermittelten Werte nicht vorhanden
	echo '{"Status":["-1","Daten wurden nicht verschickt"]}';
}
?>

update
PHP:
<?php
//Prüfen ob die über HTTP übermittelten Werte vorhanden sind
if(isset($_POST["Eintrag"]) && isset($_POST["ID"])) {

	//Mit DB Verbinden
	include("connect.php");

	//zeichen die Einfluss auf die Query haben mit ändern
	$Eintrag = $verbindung->real_escape_string($_POST["Eintrag"]);
	$ID = $verbindung->real_escape_string($_POST["ID"]);


	// SQL Query vorbereiten
	$prep = $verbindung->prepare("UPDATE ToDoListe SET Eintrag = ? WHERE ID = ?");
	
	// Variablen übergeben si = ein String und ein Integer
	if(!$prep->bind_param("si", $Eintrag, $ID)) {
		echo '{"Status":["-1","Daten entsprechen nicht dem erwarteten Format"]}';
		$verbindung->close();
		exit();
	}

	// Query abschicken und erfolg prüfen
	if($prep->execute()) {
		echo '{"Status":["2","Update erfolgreich"]}';
	} else {
		echo '{"Status":["-1","Fehler beim schreiben in die Datenbank"]}';
	}
	$verbindung->close();
}
else {
	//Fehler falls die über HTTP übermittelten Werte nicht vorhanden
	echo '{"Status":["-1","Daten wurden nicht verschickt"]}';
}
?>

delete
PHP:
<?php
//Prüfen ob die über HTTP übermittelten Werte vorhanden sind
if(isset($_POST["ID"])) {
	//Mit DB Verbinden
	include("connect.php");

	//zeichen die Einfluss auf die Query haben mit ändern
	$ID = $verbindung->real_escape_string($_POST["ID"]);

	// SQL Query vorbereiten
	$prep = $verbindung->prepare("DELETE FROM ToDoListe WHERE ID = ?");
	
	// Variablen übergeben i = ein Integer
	if(!$prep->bind_param("i", $ID)) {
		echo '{"Status":["-1","Daten entsprechen nicht dem erwarteten Format"]}';
		$verbindung->close();
		exit();
	}

	// Query abschicken und erfolg prüfen
	if($prep->execute()) {
		echo '{"Status":["3","Delete erfolgreich"]}';
	} else {
		echo '{"Status":["-1","Fehler beim schreiben in die Datenbank"]}';
	}
	$verbindung->close();
}
else {
	//Fehler falls die über HTTP übermittelten Werte nicht vorhanden
	echo '{"Status":["-1","Daten wurden nicht verschickt"]}';
}
?>
 
Zuletzt bearbeitet:
  • Danke
Reaktionen: mratix
Hallo Community,

ich bin neu hier, wie Ihr alle seht und möchte mein Tool für unsere Feuerwehr kostenfrei in PHP und MySQL programmiert habe nun auch als APP realisieren. Also muss ich ja nun auch noch JAVA lernen. Will aber nicht ganz vorne anfangen, denn ich bin ja schon 54 Jahre und möchte dementsprechend bald fertig werden. Da kam mir dieses Script als Startpunkt für die APP gerade recht. Erstmal vielen Dank, ich weiß es als Ehrenamtlicher sehr zu schätzen, wenn sich Leute uneigennützig engagieren.

Nun gut,

in meinem Android Studio ist ALLES rot. Ein Profi weiß bestimmt, was ich falsch gemacht habe, denn hier wird ja nur über das Tutorial gelobt.

Ich denke, dass die Zuordnungen und Namensgebungen oft falsch sind. Die Ausgabe im Messages Gradle Build ist leider sehr lang:


Code:
Information:Gradle tasks [:app:generateDebugSources, :app:generateDebugAndroidTestSources, :app:assembleDebug]
:app:preBuild UP-TO-DATE
:app:preDebugBuild UP-TO-DATE
:app:checkDebugManifest
:app:preReleaseBuild UP-TO-DATE
:app:prepareComAndroidSupportAppcompatV72310Library UP-TO-DATE
:app:prepareComAndroidSupportDesign2310Library UP-TO-DATE
:app:prepareComAndroidSupportRecyclerviewV72310Library UP-TO-DATE
:app:prepareComAndroidSupportSupportV42310Library UP-TO-DATE
:app:prepareDebugDependencies
:app:compileDebugAidl UP-TO-DATE
:app:compileDebugRenderscript UP-TO-DATE
:app:generateDebugBuildConfig UP-TO-DATE
:app:generateDebugAssets UP-TO-DATE
:app:mergeDebugAssets UP-TO-DATE
:app:generateDebugResValues UP-TO-DATE
:app:generateDebugResources UP-TO-DATE
:app:mergeDebugResources
:app:processDebugManifest
:app:processDebugResources
:app:generateDebugSources
:app:preDebugAndroidTestBuild UP-TO-DATE
:app:prepareDebugAndroidTestDependencies
:app:compileDebugAndroidTestAidl UP-TO-DATE
:app:processDebugAndroidTestManifest UP-TO-DATE
:app:compileDebugAndroidTestRenderscript UP-TO-DATE
:app:generateDebugAndroidTestBuildConfig UP-TO-DATE
:app:generateDebugAndroidTestAssets UP-TO-DATE
:app:mergeDebugAndroidTestAssets UP-TO-DATE
:app:generateDebugAndroidTestResValues UP-TO-DATE
:app:generateDebugAndroidTestResources UP-TO-DATE
:app:mergeDebugAndroidTestResources UP-TO-DATE
:app:processDebugAndroidTestResources UP-TO-DATE
:app:generateDebugAndroidTestSources UP-TO-DATE
:app:processDebugJavaRes UP-TO-DATE
:app:compileDebugJavaWithJavac
C:\Users\Admin\AndroidStudioProjects\NochEins\app\src\main\java\de\schoppenhonne\nocheins\DBRequestTask.java
Error:(6, 36) error: cannot find symbol class AsyncTask
Error:(14, 5) error: method does not override or implement a method from a supertype
Error:(20, 13) error: cannot find symbol class URL
Error:(20, 27) error: cannot find symbol class URL
Error:(21, 13) error: cannot find symbol class HttpURLConnection
Error:(21, 39) error: cannot find symbol class HttpURLConnection
Error:(36, 38) error: cannot find symbol variable URLEncoder
Error:(38, 38) error: cannot find symbol variable URLEncoder
Error:(40, 17) error: cannot find symbol class OutputStream
Error:(41, 17) error: cannot find symbol class BufferedWriter
Error:(41, 45) error: cannot find symbol class BufferedWriter
Error:(42, 29) error: cannot find symbol class OutputStreamWriter
Error:(50, 13) error: cannot find symbol class InputStream
Error:(51, 13) error: cannot find symbol class InputStreamReader
Error:(51, 44) error: cannot find symbol class InputStreamReader
Error:(65, 5) error: method does not override or implement a method from a supertype
C:\Users\Admin\AndroidStudioProjects\NochEins\app\src\main\java\de\schoppenhonne\nocheins\EintragAdapter.java
Error:(6, 37) error: cannot find symbol class BaseAdapter
Error:(7, 13) error: cannot find symbol class ArrayList
Error:(8, 13) error: cannot find symbol class Context
Error:(10, 27) error: cannot find symbol class Context
Error:(16, 12) error: cannot find symbol class ArrayList
Error:(36, 39) error: cannot find symbol class View
Error:(36, 57) error: cannot find symbol class ViewGroup
Error:(36, 12) error: cannot find symbol class View
Error:(12, 33) error: cannot find symbol class ArrayList
Error:(20, 5) error: method does not override or implement a method from a supertype
Error:(25, 5) error: method does not override or implement a method from a supertype
Error:(30, 5) error: method does not override or implement a method from a supertype
Error:(35, 5) error: method does not override or implement a method from a supertype
Error:(37, 9) error: cannot find symbol class View
Error:(39, 13) error: cannot find symbol class LayoutInflater
Error:(39, 40) error: cannot find symbol class LayoutInflater
Error:(40, 39) error: cannot find symbol variable Context
Error:(41, 45) error: cannot find symbol variable eintrag_layout
Error:(43, 9) error: cannot find symbol class TextView
Error:(43, 30) error: cannot find symbol class TextView
Error:(45, 9) error: cannot find symbol class View
Error:(47, 9) error: cannot find symbol class View
C:\Users\Admin\AndroidStudioProjects\NochEins\app\src\main\java\de\schoppenhonne\nocheins\MainActivity.java
Error:(29, 17) error: no suitable method found for setAdapter(EintragAdapter)
method AdapterView.setAdapter(ListAdapter) is not applicable
(argument mismatch; EintragAdapter cannot be converted to ListAdapter)
method AbsListView.setAdapter(ListAdapter) is not applicable
(argument mismatch; EintragAdapter cannot be converted to ListAdapter)
method ListView.setAdapter(ListAdapter) is not applicable
(argument mismatch; EintragAdapter cannot be converted to ListAdapter)
Error:(34, 61) error: cannot find symbol variable basis_url
Error:(42, 41) error: cannot find symbol variable main
Error:(80, 51) error: cannot find symbol variable basis_url
Error:(112, 55) error: cannot find symbol variable basis_url
Error:(127, 39) error: cannot find symbol variable basis_url
Error:(149, 35) error: cannot find symbol method notifyDataSetChanged()
Error:Execution failed for task ':app:compileDebugJavaWithJavac'.
> Compilation failed; see the compiler error output for details.
Information:BUILD FAILED
Information:Total time: 2.304 secs
Information:46 errors
Information:0 warnings
Information:See complete output in console


Ist hier sehr viel zu ändern und anzupassen, oder schaffe ich das mit etwas Unterstützung von Euch?

Ich habe schon ein paar Anleitungen und Skripte gefunden. Aber immer mit Fehlermeldunegn verbunden. Wie:
darf nicht im Main Thread ausgeführt werden
oder:
there was internal server error while processing your request

Und immer wieder das rote R. Ihr kennt das ja.

Über eine Nachricht freue ich mich. Wenn weitere Infos benötigt werden, kein Problem. Übrigens helfe ich gerne bei dem, was ich kann - siehe oben.
 
Zuletzt bearbeitet von einem Moderator:
Du hättest ruhig ein neues Thema im normalen Forumsbereich aufmachen können mit einem Verweis auf diesen Thread. Aber nun gut, die Informationen von dir sind nicht ausreichend für eine Analyse deines Problems. Außer das hier:

"Und immer wieder das rote R"

Womöglich das falsche R importiert also die Anweisung "import android.R" steht da wohl bei dir im Sourcecode ganz oben, oder?!
 
Sieht so aus als würden die Imports in deinen Klassen fehlen (zumindest zum teil). Probier mal Strg+Alt+O. Aber Jaiel hat recht mach lieber ein Post im Hauptforum, da bemerken es auch mehr Leute
 
Zunächst erstmal vielen Dank für die schnelle Reaktion.
Ich würde ja gerne einen neuen Post im Hauptforum machen, aber wo? Ich finde keinen Link. Oder habe ich nicht die Rechte?

Übrigens habe ich nicht bei diesem Projekt das Problem mit dem roten R. Hier sind viele andere Begriffe rot.

In der MainActivity: execute, basis_url und main.
In der EintragAdapter: BaseAdapter, ArrayList, Context, size, get, View, ViewGroup, LayoutInflater, getSystemService, Context, inlate, eintrag_layout, TextView, findViewById, setText und setTag.
Und in der DBRequestTask sind es z.Bsp.: AsyncTask und URL.

Keine Ahnung, wo man die Beziehungen erstellt.

Wie und wo kann ich meine Fragen posten?
 
Oberhalb des ersten Themas und unterhalb des letzten auf jeder Seite gibt es einen blauen Button mit der Aufschrift "Neues Thema erstellen"

Zu den roten Sachen, bin ich relativ sicher, dass das an den Imports liegt, wenn Strg+Alt+O die nicht organisiert, dann solltest du mit Alt+Enter aber wenigstens QuickFix Vorschläge für jedes einzelne Problem bekommen.
 
  • Danke
Reaktionen: FeuerwehrAdmin
Ich habe dem Team mal geschrieben dass die es für dich verschieben. Kannst ja deinen ersten Post editieren und einen Link zu diesem Thread heir mit rein tun

Poste unbedingt deinen Code der Activity oder so
 
  • Danke
Reaktionen: FeuerwehrAdmin
Okay, danke Euch beiden für Eure Tips. Ich habe mal im neuen Thread die MainActicty gepostet.
 

Ähnliche Themen

Muecke1982
Antworten
0
Aufrufe
388
Muecke1982
Muecke1982
H
Antworten
2
Aufrufe
1.057
swa00
swa00
BulliM
  • BulliM
2 3
Antworten
44
Aufrufe
3.674
BulliM
BulliM
Zurück
Oben Unten