Eine Bundesligatabelle als Android Content Provider – Teil 2: Administration

1. Aufgabe

Nach der Betrachtung der Datenhaltung einer Bundesligatabelle im Kontext eines Android Content Providers gehen wir im zweiten Teil dieser Fallstudie näher auf die Administration dieser Daten ein. Neben dem lesenden und schreibenden Zugriff auf die Daten (query, insert, update und delete) soll auch auf das Vergeben von spezifischen Zugriffsrechten demonstriert werden, die ein Android Content Provider festlegen kann und die von einem Client des Providers angefordert werden müssen. Im Falle einer administrierenden App soll diese sowohl schreibend wie auch lesend auf die Daten des Content Providers zugreifen dürfen.

An der Oberfläche der Hauptaktivität (Abbildung 1) lässt sich bereits gut erkennen, dass die Bedienaktionen auf das Hinzufügen von Mannschaften und Spielergebnissen zugeschnitten sind, also vor allem auf schreibende Zugriffe:

Oberfläche der Hauptaktivität der League Data Provider Administrations-App.

Abbildung 1. Oberfläche der Hauptaktivität der „League Data Provider“ Administrations-App.


Für das Hinzufügen einer Mannschaft habe ich aus Aufwandsgründen auf eine separate Aktivität verzichtet. Diese müsste, wenn Sie etwas Vergleichbares für Ihre Lieblingssportart planen, in Ihrer Realisierung noch ergänzt werden. Das Eintragen eines Spielergebnisses ist dafür vollumfänglich implementiert. Eine Vorgabe, die als Grundlage für Ihre eigene Realisierung dienen könnte, finden Sie in Abbildung 2 vor:

Eingabe eines Spielergebnisses.

Abbildung 2. Eingabe eines Spielergebnisses.


Entwickeln Sie im zweiten Teil dieser Fallstudie eine Android-App, die – ganz im Sinne einer Administratoren-App – alle relevanten Daten eines Ligaspielbetriebs einlesen kann. Achten Sie dabei auch darauf, dass diese App entsprechende Rechte besitzt, um lesend und schreibend auf die Daten des Content Providers zugreifen zu dürfen.

2. Lösung

Bei der Administrierung des Content Providers lassen sich verschiede Wege einschlagen. In einer sehr flexibel gehaltenen App könnte man zum Beispiel alle Daten (Mannschaften und Spielergebnisse) eines Ligabetriebs dialoggeführt eingeben. In der nachfolgend vorgestellten Lösung beschreite ich einen Zwischenweg: Die Informationen der am Spielbetrieb beteiligten Mannschaften sind fest in der App einprogrammiert – vor allem um die beispielhafte Realisierung einfacher und kürzer zu gestalten. Die Eingabe eines Spielergebnisses hingegen erfolgt im Dialog mit dem Anwender.

Nur zu Testzwecken finden Sie auch Schaltflächen zum Löschen der Mannschaften sowie für die Eingabe einer Reihe fest einprogrammierter Spielergebnisse vor. Im produktiven Einsatz der App ergeben diese Schaltflächen keinen Sinn – sehr wohl aber zum Testen der Implementierung.

Bevor wir auf die eigentliche Implementierung zu sprechen kommen, handeln wir ein prinzipielles Problem beim Zugriff auf den Content Provider ab: Im Projekt League Data Provider: Bereitstellung der Daten – also bei der Hosting-App des Providers – hatten wir für die Spaltennamen der beteiligten Tabellen konkrete Namen vergeben wie etwa NAME oder ID. Unelegant wäre es, diese Namen in anderen Projekten, die auf den Content Provider zugreifen wollen, einfach händisch zu kopieren. Bei Änderungen in der Datenbasis des Content Providers kommt es dann zu Fehlern beim Zugriff. Oder anders herum formuliert: Wie lassen sich die für den Zugriff notwendigen Bezeichner (Spaltennamen) softwaretechnisch sauber exportieren? Ein Ansatz sieht so aus, diese Namen mittels öffentlicher statischer Klassenvariablen in der Definitionsklasse des Content Providers zu definieren und diese dann in den referenzierenden Projekten zu importieren. In der Eclipse-Entwicklungsumgebung geht dies so:

  • Rechts-Klick auf dem Verzeichnis des Projekts Content Provider Administration im Package Explorer der Eclipse-Entwicklungsumgebung: Anwahl von Properties.

  • Im Dialogfenster „Properties for LeagueDataAdmin“ links Klick auf „Java Build Path“ und rechts Selektion des Reiters „Projects“ (Abbildung 3):

    Dialogfenster Properties for LeagueDataAdmin.

    Abbildung 3. Dialogfenster „Properties for LeagueDataAdmin“.

  • Anwahl der Schaltfläche „Add ...

  • Selektion des Projekts LeagueDataProvider im Dialogfenster Required Project Selection (Abbildung 4) mit anschließender Ok-Quittierung:

    Dialogfenster Required Project Selection.

    Abbildung 4. Dialogfenster „Required Project Selection“.

  • Wiederholte Ok-Quittierung im Dialogfenster Required Project Selection und im überlagerten Dialogfenster Properties for LeagueDataAdmin:

    Dialogfenster Properties for LeagueDataAdmin in aktualisierter Darstellung.

    Abbildung 5. Dialogfenster „Properties for LeagueDataAdmin“ in aktualisierter Darstellung.

Auf diese Weise lässt sich das Host-Projekt – oder öffentliche Teile desselben – in anderen Apps importieren.

Für die Start-Aktivität der App benötigen wir eine Layout-Datei zur Beschreibung der Oberfläche, eine Java-Datei für den Logik-Anteil sowie eine Datei zur Ablage der Zeichenketten-Ressourcen.

01: <RelativeLayout 
02:     xmlns:android="http://schemas.android.com/apk/res/android"
03:     xmlns:tools="http://schemas.android.com/tools"
04:     android:layout_width="match_parent"
05:     android:layout_height="match_parent"
06:     android:orientation="vertical" >
07: 
08:     <Button
09:         android:id="@+id/button_insert_all_teams"
10:         android:layout_width="match_parent"
11:         android:layout_height="wrap_content"
12:         android:text="@string/buttonInsertAllTeams" />    
13:         
14:     <Button
15:         android:id="@+id/button_delete_all_teams"
16:         android:layout_width="match_parent"
17:         android:layout_height="wrap_content"
18:         android:layout_below="@+id/button_insert_all_teams"
19:         android:text="@string/buttonDeleteAllTeams" />        
20:     
21:     <Button
22:         android:id="@+id/button_enter_all_games"
23:         android:layout_width="match_parent"
24:         android:layout_height="wrap_content"
25:         android:layout_below="@+id/button_delete_all_teams"
26:         android:text="@string/buttonEnterAllGamesTest" />      
27:         
28:     <Button
29:         android:id="@+id/button_delete_all_games"
30:         android:layout_width="match_parent"
31:         android:layout_height="wrap_content"
32:         android:layout_below="@+id/button_enter_all_games"
33:         android:text="@string/buttonDeleteAllGamesTest" />  
34:         
35:     <Button
36:         android:id="@+id/button_delete_single_game"
37:         android:layout_width="match_parent"
38:         android:layout_height="wrap_content"
39:         android:layout_below="@+id/button_delete_all_games"
40:         android:text="@string/buttonDeleteSingleGame" />  
41:         
42:     <Button
43:         android:id="@+id/button_insert_single_game"
44:         android:layout_width="match_parent"
45:         android:layout_height="wrap_content"
46:         android:layout_below="@+id/button_delete_single_game"
47:         android:text="@string/buttonInsertSingleGame" /> 
48:         
49:    <RelativeLayout 
50:         android:id="@+id/InnerRelativeLayout"
51:         android:layout_width="wrap_content"
52:         android:layout_height="wrap_content"
53:         android:layout_alignParentBottom="true" >
54:         
55:         <TextView        
56:             android:id="@+id/textViewStatus"
57:             android:layout_width="match_parent"
58:             android:layout_height="wrap_content"
59:             android:padding="10dp"
60:             android:textSize="18sp" />
61:     
62:     </RelativeLayout>      
63:          
64: </RelativeLayout >

Beispiel 1. Datei activity_main.xml: UI der Hauptaktivität (Content Administrator App).


001: package com.example.leaguedataadmin;
002: 
003: import com.example.leaguedataprovider.LeagueDataContract;
004: 
005: import android.app.Activity;
006: import android.content.ContentResolver;
007: import android.content.ContentValues;
008: import android.content.Intent;
009: import android.net.Uri;
010: import android.os.Bundle;
011: import android.view.View;
012: import android.view.View.OnClickListener;
013: import android.widget.Button;
014: import android.widget.TextView;
015: 
016: public class MainActivity extends Activity implements OnClickListener {
017:     
018:     private TextView textViewStatus;
019:     
020:     public MainActivity() {
021:         super();
022:         System.out.println ("c'tor MainActivity() LeagueData Admin");
023:     }
024:     
025:     @Override
026:     protected void onCreate(Bundle savedInstanceState) {
027:         super.onCreate(savedInstanceState);
028:         this.setContentView(R.layout.activity_main);
029:         
030:         this.textViewStatus = (TextView) this.findViewById(R.id.textViewStatus);
031:         
032:         // connect event handler
033:         Button b = (Button) this.findViewById(R.id.button_insert_all_teams);
034:         b.setOnClickListener(this);
035:         b = (Button) this.findViewById(R.id.button_delete_all_teams);
036:         b.setOnClickListener(this);
037:         b = (Button) this.findViewById(R.id.button_insert_single_game);
038:         b.setOnClickListener(this);
039:         b = (Button) this.findViewById(R.id.button_enter_all_games);
040:         b.setOnClickListener(this);
041:         b = (Button) this.findViewById(R.id.button_delete_all_games);
042:         b.setOnClickListener(this);
043:         b = (Button) this.findViewById(R.id.button_delete_single_game);
044:         b.setOnClickListener(this);
045:     }
046: 
047:     @Override
048:     public void onClick(View v) {
049: 
050:         int id = v.getId(); 
051:         if (id == R.id.button_insert_all_teams) {
052: 
053:             this.addAllTeams();
054:         } 
055:         else if (id == R.id.button_delete_all_teams) {
056:             
057:             this.deleteAllTeams();
058:         }
059:         else if (id == R.id.button_insert_single_game) {
060:             
061:             Intent gameResultIntent = new Intent(this, GameResultActivity.class);
062:             this.startActivity(gameResultIntent);
063:         }
064:         else if (id == R.id.button_enter_all_games) {
065:             
066:             this.enterAllGamesTest();
067:         }
068:         else if (id == R.id.button_delete_all_games) {
069:             
070:             this.deleteAllGamesTest();
071:         }
072:         else if (id == R.id.button_delete_single_game) {
073:             
074:             this.deleteSingleTeamTest();
075:         }
076:     }
077: 
078:     private void addAllTeams () {
079:         
080:         this.addSingleTeam (1, "Rhein-Neckar Löwen", "Mannheim");
081:         this.addSingleTeam (2, "THW Kiel", "Kiel");
082:         this.addSingleTeam (3, "FRISCH AUF! Göppingen", "Göppingen");
083:         this.addSingleTeam (4, "SG Flensburg-Handewitt", "Flensburg");
084:         this.addSingleTeam (5, "Füchse Berlin", "Berlin");
085:         this.addSingleTeam (6, "SC Magdeburg", "Magdeburg");
086:         this.addSingleTeam (7, "HSV Handball", "Hamburg");
087:         this.addSingleTeam (8, "TSV Hannover-Burgdorf", "Burgdorf");
088:         this.addSingleTeam (9, "VfL Gummersbach", "Gummersbach");
089:         this.addSingleTeam (10, "MT Melsungen", "Melsungen");
090:         this.addSingleTeam (11, "TuS N-Lübbecke", "Lübbecke");
091:         this.addSingleTeam (12, "HBW Balingen-Weilstetten", "Balingen");
092:         this.addSingleTeam (13, "Bergischer HC", "Wuppertal/Solingen");
093:         this.addSingleTeam (14, "HC Erlangen", "Erlangen");
094:         this.addSingleTeam (15, "HSG Wetzlar", "Wetzlar");
095:         this.addSingleTeam (16, "TSV GWD Minden", "Minden");
096:         this.addSingleTeam (17, "TBV Lemgo", "Lemgo");
097:         this.addSingleTeam (18, "TSG Lu-Friesenheim", "Ludwigshafen");
098:         this.addSingleTeam (19, "SG BBM Bietigheim", "Bietigheim");
099:         
100:         this.textViewStatus.setText("Done: 19 teams added !");
101:     }
102:     
103:     private void addSingleTeam (long id, String name, String city) {
104:         
105:         // create a new teams record
106:         ContentValues values = new ContentValues();
107:         values.put(LeagueDataContract.Teams._ID, id);
108:         values.put(LeagueDataContract.Teams.NAME, name);
109:         values.put(LeagueDataContract.Teams.CITY, city);
110:         
111:         // add record into data provider repository
112:         ContentResolver cr = this.getContentResolver();
113:         Uri uriTeams = Uri.parse(LeagueDataContract.CONTENT_TEAMS);
114:         Uri result = cr.insert(uriTeams, values);
115:         System.out.println("inserted team: " + result.toString());
116:     }
117:     
118:     private void deleteAllTeams () {
119:         
120:         // don't specify a WHERE clause
121:         ContentResolver cr = this.getContentResolver();
122:         Uri uriTeams = Uri.parse(LeagueDataContract.CONTENT_TEAMS);        
123:         int count = cr.delete(uriTeams, null, null);
124:         this.textViewStatus.setText("Done: " + count + " teams deleted !");
125:     }    
126:     
127:     private void deleteSingleTeamTest () {
128:         
129:         Uri uriTeams = Uri.parse(LeagueDataContract.CONTENT_TEAMS);
130:         Uri team = Uri.withAppendedPath (uriTeams, "2");
131:         
132:         // don't specify a WHERE clause
133:         ContentResolver cr = this.getContentResolver();
134:         int count = cr.delete(team, null, null);
135:         
136:         this.textViewStatus.setText("deleteSingleTeamTest: " + count + " have been deleted !");
137:     }  
138:  
139:     private void enterAllGamesTest () {
140: 
141:         ContentResolver cr = this.getContentResolver();
142:         
143:         // 1. Spieltag
144:         MainActivity.addGameResultRecord (cr, 4, 18, 29, 23, 1);
145:         MainActivity.addGameResultRecord (cr, 8, 10, 22, 26, 1);
146:         MainActivity.addGameResultRecord (cr, 13, 19, 33, 27, 1);
147:         MainActivity.addGameResultRecord (cr, 17, 2, 27, 21, 1);
148:         MainActivity.addGameResultRecord (cr, 14, 11, 25, 30, 1);
149:         MainActivity.addGameResultRecord (cr, 3, 5, 29, 27, 1);
150:         MainActivity.addGameResultRecord (cr, 9, 7, 27, 27, 1);
151:         MainActivity.addGameResultRecord (cr, 1, 6, 24, 23, 1);
152:         MainActivity.addGameResultRecord (cr, 16, 15, 24, 30, 1);
153:         MainActivity.addGameResultRecord (cr, 10, 12, 31, 20, 1);
154: 
155:         // ............. usw. usw. usw.
156:         
157:         this.textViewStatus.setText("Done: 122 games added !");
158:     }
159: 
160:     private void deleteAllGamesTest () {
161:         
162:         // don't specify a WHERE clause
163:         ContentResolver cr = this.getContentResolver();
164:         Uri uriGames = Uri.parse(LeagueDataContract.CONTENT_GAMES);
165:         int count = cr.delete(uriGames, null, null);
166:         this.textViewStatus.setText("Done: " + count + " games deleted !");
167:     }
168: 
169:     public static void addGameResultRecord (
170:             ContentResolver cr, int teamHome, int teamAway,
171:             int goalsHome, int goalsAway, long matchDay) {
172:         
173:         ContentValues values = new ContentValues();
174:         values.put(LeagueDataContract.Games.TEAMHOME, teamHome);
175:         values.put(LeagueDataContract.Games.TEAMAWAY, teamAway);
176:         values.put(LeagueDataContract.Games.GOALSHOME, goalsHome);
177:         values.put(LeagueDataContract.Games.GOALSAWAY, goalsAway);
178:         values.put(LeagueDataContract.Games.MATCHDAY, matchDay);
179:         
180:         Uri uriGames = Uri.parse(LeagueDataContract.CONTENT_GAMES);
181:         Uri result = cr.insert(uriGames, values);
182:         System.out.println("inserted game: " + result.toString());
183:     }  
184: }

Beispiel 2. Datei MainActivity.java: Logik-Anteil der Hauptaktivität (Content Administrator App).


Für den Zugriff auf den Content Provider betrachten wir exemplarisch die Zeilen 106 bis 114 von Listing 2. Um aus einer App auf einen Content Provider in einer anderen App zugreifen zu können, benötigt man zuerst einmal eine Instanz der Klasse ContentResolver. Objekte dieser Klasse stellen dem Client die vier zentralen CRUD-Methoden (create, read, update und delete) in seinem Prozessraum zur Verfügung. An Hand der Autoritäts-Zeichenkette(n) und den Informationen in der Manifest-Datei des Providers ist ein ContentResolver-Objekt in der Lage, eine Verbindung zum Prozess des Providers herzustellen und einen CRUD-Methodenaufruf über Prozessgrenzen hinweg zum Provider-Prozess durchzuschleusen. Die Schnittstelle der ContentResolver-Klasse wurde so ausgelegt, dass zwischen den CRUD-Methoden des Content Providers und denen des ContentResolver-Objekts eine 1:1-Beziehung besteht. Man kann auch sagen, dass das ContentResolver-Objekt eine Art Proxy-Objekt für den dazugehörigen Content Provider darstellt.

Um prozess-spezifisch eine ContentResolver-Instanz anzulegen, gibt es in der Activity-Klasse die getContentResolver-Methode. Zum Einfügen eines neuen Datensatzes gibt es die insert-Methode. Sie bestimmt mit Hilfe der Autoritäts-Zeichenkette den gesuchten Content Provider und transportiert die Parameter des insert-Methodenaufrufs über die Prozessgrenzen hinweg. Für die Parameter selbst gibt es eine Klasse ContentValues. Die überladene put-Methode nimmt zu einem bestimmten Spaltennamen (Schlüssel) einen Wert beliebigen Typs in das Container-Objekt auf. Als Resultat des insert-Methodenaufrufs erhält man ein Uri-Objekt zurück, das gemäß der Content Provider Richtlinien den Datensatz in Gestalt einer URI beschreibt.

In Listing 3 vervollständigen wir diese Betrachtungen mit der Datei für die Zeichenketten-Ressourcen:

01: <?xml version="1.0" encoding="utf-8"?>
02: 
03: <resources>
04: 
05:     <string name="app_name">LeagueData: Content Admin</string>
06:     <string name="action_settings">Settings</string>
07:     
08:     <string name="buttonInsertAllTeams">Enter all Teams</string>
09:     <string name="buttonDeleteAllTeams">Delete all Teams</string>
10:     <string name="buttonInsertSingleGame">Insert a single Game</string>
11:     <string name="buttonEnterAllGamesTest">Enter all Games (Test)</string>
12:     <string name="buttonDeleteAllGamesTest">Delete all Games (Test)</string>
13:     <string name="buttonDeleteSingleGame">Delete a single Games (Test)</string>
14:     
15:     <string name="textViewInitialStatus"></string>
16:     <string name="title_activity_game_result">LeagueData Admin Client: Game Result Dialog</string>
17:     <string name="label_first_team">1. Team:</string>
18:     <string name="label_second_team">2. Team:</string>
19:     <string name="label_goals_first_team">Tore 1. Team:</string>
20:     <string name="label_goals_second_team">Tore 2. Team:</string>
21:     <string name="label_matchday">Spieltag:</string>
22:     <string name="button_enter_game">Enter Game</string>
23: </resources>

Beispiel 3. Datei strings.xml: Zeichenketten-Resources der Content Administrator App.


Eine Besonderheit des vorliegenden Content Providers besteht darin, dass er gewisse Zugriffsrechte benötigt, um sicherzustellen, dass nicht alle Apps beliebig auf die Daten zugreifen können. Im vorliegenden Fall der Administrator-App benötigen wir sowohl lesenden wie auch schreibenden Zugriff auf den Provider. Dementsprechend müssen wir der Manifest-Datei zwei uses-permission-Einträge hinzufügen, die Sie in Listing 4 in den Zeilen 8 und 9 vorfinden:

01: <?xml version="1.0" encoding="utf-8"?>
02: 
03: <manifest xmlns:android="http://schemas.android.com/apk/res/android"
04:     package="com.example.leaguedataadmin"
05:     android:versionCode="1"
06:     android:versionName="0.60" >
07:     
08:     <uses-permission android:name="com.example.leaguedataprovider.READ_ONLY" />
09:     <uses-permission android:name="com.example.leaguedataprovider.WRITE" />
10:    
11:     <uses-sdk
12:         android:minSdkVersion="8"
13:         android:targetSdkVersion="19" />
14: 
15:     <application
16:         android:allowBackup="true"
17:         android:icon="@drawable/ic_launcher"
18:         android:label="@string/app_name"
19:         android:theme="@style/AppTheme" >
20: 
21:         <activity
22:             android:name="com.example.leaguedataadmin.MainActivity"
23:             android:label="@string/app_name" >
24:             <intent-filter>
25:                 <action android:name="android.intent.action.MAIN" />
26:                 <category android:name="android.intent.category.LAUNCHER" />
27:             </intent-filter>
28:         </activity>
29:         
30:         <activity
31:             android:name="com.example.leaguedataadmin.GameResultActivity"
32:             android:label="@string/title_activity_game_result"
33:             android:parentActivityName="com.example.leaguedataadmin.MainActivity" >
34:             <meta-data
35:                 android:name="android.support.PARENT_ACTIVITY"
36:                 android:value="com.example.leaguedataadmin.MainActivity" />
37:         </activity>
38:     </application>
39: </manifest>

Beispiel 4. Datei AndroidManifest.xml: Manifest-Datei der Content Administrator App.


Diese Einstellungen in der Manifest-Datei (Listing 4) spiegeln sich auch in den System-Einstellungen wieder, die Sie in den Android-App Einstellungen unter der Rubrik Apps vorfinden, siehe dazu die Abbildung 6 am unteren Rand:

System-Einstellungen der Administrator App.

Abbildung 6. System-Einstellungen der Administrator App.


Damit fehlt nur noch die Betrachtung der zweiten Aktivität dieser App, um ein Spielresultat im Content Provider eintragen zu können. Wir kommen dieses Mal mit einer Layout-Datei zur Beschreibung der Oberfläche (Listing 5) und einer zweiten Datei für den Logik-Anteil (Listing 6) aus:

01: <TableLayout
02:     xmlns:android="http://schemas.android.com/apk/res/android"
03:     android:layout_width="fill_parent"
04:     android:layout_height="fill_parent" >
05:      
06:     <TableRow android:layout_margin="6dp">
07:         
08:         <TextView
09:             android:layout_width="160dp"
10:             android:textSize="20sp"
11:             android:layout_height="wrap_content"
12:             android:text="@string/label_first_team" />
13:    
14:         <Spinner
15:             android:id="@+id/spinnerFirstTeam"
16:             android:layout_width="fill_parent"
17:             android:layout_height="wrap_content"
18:             android:layout_weight="1"
19:             android:drawSelectorOnTop="true" />
20:             
21:     </TableRow>
22:     
23:     <TableRow android:layout_margin="6dp">
24:         
25:         <TextView
26:             android:textSize="20sp"
27:             android:layout_height="wrap_content"
28:             android:text="@string/label_second_team" />
29:         
30:         <Spinner
31:             android:id="@+id/spinnerSecondTeam"
32:             android:layout_width="match_parent"
33:             android:layout_height="wrap_content"
34:             android:layout_weight="1"
35:             android:drawSelectorOnTop="true" />
36:     
37:     </TableRow>
38:     
39:     <TableRow android:layout_margin="6dp">
40:         
41:         <TextView
42:             android:textSize="20sp"
43:             android:layout_height="wrap_content"
44:             android:text="@string/label_goals_first_team" />
45:         
46:         <EditText
47:             android:id="@+id/textViewFirstTeam"
48:             android:layout_width="match_parent"
49:             android:layout_height="wrap_content"
50:             android:layout_weight="1"
51:             android:inputType="number" />
52: 
53:     </TableRow>
54:     
55:     <TableRow android:layout_margin="6dp">
56:         
57:         <TextView
58:             android:textSize="20sp"
59:             android:layout_height="wrap_content"
60:             android:text="@string/label_goals_second_team" />
61:         
62:         <EditText
63:             android:id="@+id/textViewSecondTeam"
64:             android:layout_width="match_parent"
65:             android:layout_height="wrap_content"
66:             android:layout_weight="1"
67:             android:inputType="number" />
68: 
69:     </TableRow>
70:     
71:     <TableRow android:layout_margin="6dp">
72:         
73:         <TextView
74:             android:textSize="20sp"
75:             android:layout_height="wrap_content"
76:             android:text="@string/label_matchday" />
77:         
78:         <Spinner
79:             android:id="@+id/spinnerMatchday"
80:             android:layout_width="match_parent"
81:             android:layout_height="wrap_content"
82:             android:layout_weight="1"
83:             android:drawSelectorOnTop="true" />
84: 
85:     </TableRow>
86:     
87:     <TableRow android:layout_margin="6dp">
88:         
89:         <Button
90:             android:id="@+id/buttonEnterGame"
91:             android:textSize="20sp"
92:             android:layout_height="wrap_content"
93:             android:text="@string/button_enter_game"
94:             android:layout_weight="1"
95:             android:layout_span="2" />
96:         
97:     </TableRow>
98:     
99: </TableLayout>

Beispiel 5. Datei activity_game_result.xml: UI der Aktivität „Spielresultat hinzufügen“ (Content Administrator App).


001: package com.example.leaguedataadmin;
002: 
003: import java.util.ArrayList;
004: import java.util.List;
005: 
006: import com.example.leaguedataprovider.LeagueDataContract;
007: 
008: import android.app.Activity;
009: import android.content.ContentResolver;
010: import android.content.Context;
011: import android.database.Cursor;
012: import android.net.Uri;
013: import android.os.Bundle;
014: import android.text.TextUtils;
015: import android.view.View;
016: import android.view.View.OnClickListener;
017: import android.widget.ArrayAdapter;
018: import android.widget.Button;
019: import android.widget.Spinner;
020: import android.widget.TextView;
021: import android.widget.Toast;
022: 
023: public class GameResultActivity extends Activity implements OnClickListener {
024:     
025:     private Spinner spinnerFirstTeam;
026:     private Spinner spinnerSecondTeam;
027:     private Spinner spinnerMatchDay;
028:     
029:     private TextView textViewFirstTeam;
030:     private TextView textViewSecondTeam;
031: 
032:     @Override
033:     protected void onCreate(Bundle savedInstanceState) {
034:         super.onCreate(savedInstanceState);
035:         this.setContentView(R.layout.activity_game_result);
036:         
037:         // retrieve widget references
038:         this.spinnerFirstTeam = (Spinner) this.findViewById(R.id.spinnerFirstTeam);
039:         this.spinnerSecondTeam = (Spinner) this.findViewById(R.id.spinnerSecondTeam);
040:         this.spinnerMatchDay = (Spinner) this.findViewById(R.id.spinnerMatchday);
041:         this.textViewFirstTeam = (TextView) this.findViewById(R.id.textViewFirstTeam);
042:         this.textViewSecondTeam = (TextView) this.findViewById(R.id.textViewSecondTeam);
043:         
044:         List<String> names = this.retrieveListOfTeams ();        
045:         this.fillSpinnerWithTeams(this.spinnerFirstTeam, names);
046:         this.fillSpinnerWithTeams(this.spinnerSecondTeam, names);
047:         this.fillSpinnerMatchDays(this.spinnerMatchDay, 2 * (names.size() - 1));
048:         
049:         // connect event handler
050:         Button b = (Button) this.findViewById(R.id.buttonEnterGame);
051:         b.setOnClickListener(this);        
052:     }
053:     
054:     @Override
055:     public void onClick(View v) {
056:         
057:         int id = v.getId(); 
058:         if (id == R.id.buttonEnterGame) {
059:             this.enterGame();
060:         } 
061:     }    
062:     
063:     // helper methods
064:     private List<String> retrieveListOfTeams () {
065:         
066:         List<String> teamNames = new ArrayList<String>();
067:         
068:         // retrieve list of teams from data league content provider
069:         ContentResolver cr = this.getContentResolver();
070:         String resultColumns[] = new String[] { LeagueDataContract.Teams.NAME };        
071:         Uri uriTeams = Uri.parse(LeagueDataContract.CONTENT_TEAMS);
072:         Cursor cursor = cr.query(uriTeams, resultColumns, "", null, "");       
073:         
074:         if (cursor == null) {
075:             System.out.println ("  Internal ERROR: Cursor IS NULL");
076:         }
077:         else {
078:             if (! cursor.moveToFirst()) {
079:                 System.out.println("  No content currently !");
080:             }
081:             else {
082:                 
083:                 int nameColIndex = cursor.getColumnIndex (LeagueDataContract.Teams.NAME);
084:                 
085:                 do {
086:                     teamNames.add(cursor.getString (nameColIndex));
087:                 }
088:                 while (cursor.moveToNext());
089:             }
090:                 
091:             if (cursor != null)
092:                 cursor.close();
093:         }
094:         
095:         return teamNames;        
096:     }
097: 
098:     private void fillSpinnerWithTeams (Spinner spinner, List<String> names) {
099: 
100:         // poke list of names into spinner widget
101:         ArrayAdapter<String> arrayAdapter =
102:             new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, names);
103:         spinner.setAdapter(arrayAdapter);
104:     }
105:     
106:     private void fillSpinnerMatchDays (Spinner spinner, int numMatchDays) {
107:         
108:         List<String> matchDays = new ArrayList<String>();
109:         for (int i = 0; i < numMatchDays; i ++) {
110:             Integer n = Integer.valueOf(i + 1);
111:             matchDays.add(n.toString());
112:         }
113: 
114:         // poke list of names into spinner widget
115:         ArrayAdapter<String> arrayAdapter =
116:             new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, matchDays);
117:         spinner.setAdapter(arrayAdapter);
118:     }
119:     
120:     private void enterGame() {
121: 
122:         int idFirstTeam = ((int) this.spinnerFirstTeam.getSelectedItemId()) + 1;
123:         int idSecondTeam = ((int) this.spinnerSecondTeam.getSelectedItemId()) + 1;
124:         
125:         int matchDay = ((int) this.spinnerMatchDay.getSelectedItemId()) + 1;
126:         
127:         String goalsFirstTeam = this.textViewFirstTeam.getText().toString();
128:         String goalsSecondTeam = this.textViewSecondTeam.getText().toString();
129:         
130:         if (TextUtils.isEmpty(goalsFirstTeam)) {
131:         
132:             Context context = this.getApplicationContext();
133:             Toast.makeText(context, "Enter goals of first team !", Toast.LENGTH_LONG).show();
134:             return;
135:         }
136:         
137:         if (TextUtils.isEmpty(goalsSecondTeam)) {
138:             
139:             Context context = this.getApplicationContext();
140:             Toast.makeText(context, "Enter goals of second team !", Toast.LENGTH_LONG).show();
141:             return;
142:         }        
143: 
144:         int goalsFirst = Integer.parseInt(goalsFirstTeam);
145:         int goalsSecond = Integer.parseInt(goalsSecondTeam);
146:         
147:         // enter record into content provider
148:         ContentResolver cr = this.getContentResolver();
149:         MainActivity.addGameResultRecord(
150:             cr, idFirstTeam, idSecondTeam, goalsFirst, goalsSecond, matchDay);
151: 
152:         // reset user interface
153:         this.spinnerFirstTeam.setSelection(0);
154:         this.spinnerSecondTeam.setSelection(0);
155:         this.spinnerMatchDay.setSelection(0);
156:         
157:         this.textViewFirstTeam.setText("");
158:         this.textViewSecondTeam.setText("");
159:     }
160: }

Beispiel 6. Datei GameResultActivity.java: Logik-Anteil der Aktivität „Spielresultat hinzufügen“.


Abschließende Bemerkung: Sollten Sie sich tatsächlich die Mühe machen wollen, den Tabellenstand der ersten Handballbundesliga am 12. November 2014 nachstellen oder überprüfen zu wollen, finden Sie abschließend den kompletten Quellcode der Methode enterAllGamesTest aus der Klasse MainActivity vor:

001: private void enterAllGamesTest () {
002: 
003:     ContentResolver cr = this.getContentResolver();
004:     
005:     /*
006:      *  Erfassung der Daten: 12. November 2014 / www.dkb-handball-bundesliga.de 
007:      */
008:     
009:     // 1. Spieltag
010:     MainActivity.addGameResultRecord (cr, 4, 18, 29, 23, 1);
011:     MainActivity.addGameResultRecord (cr, 8, 10, 22, 26, 1);
012:     MainActivity.addGameResultRecord (cr, 13, 19, 33, 27, 1);
013:     MainActivity.addGameResultRecord (cr, 17, 2, 27, 21, 1);
014:     MainActivity.addGameResultRecord (cr, 14, 11, 25, 30, 1);
015:     MainActivity.addGameResultRecord (cr, 3, 5, 29, 27, 1);
016:     MainActivity.addGameResultRecord (cr, 9, 7, 27, 27, 1);
017:     MainActivity.addGameResultRecord (cr, 1, 6, 24, 23, 1);
018:     MainActivity.addGameResultRecord (cr, 16, 15, 24, 30, 1);
019:     MainActivity.addGameResultRecord (cr, 10, 12, 31, 20, 1);
020:     
021:     // 2. Spieltag
022:     MainActivity.addGameResultRecord (cr, 2, 4, 30, 26, 2);
023:     MainActivity.addGameResultRecord (cr, 19, 1, 22, 32, 2);
024:     MainActivity.addGameResultRecord (cr, 15, 17, 32, 25, 2);
025:     MainActivity.addGameResultRecord (cr, 18, 3, 23, 29, 2);
026:     MainActivity.addGameResultRecord (cr, 7, 8, 23, 23, 2);
027:     MainActivity.addGameResultRecord (cr, 5, 13, 32, 23, 2);
028:     MainActivity.addGameResultRecord (cr, 10, 9, 26, 27, 2);
029:     MainActivity.addGameResultRecord (cr, 6, 14, 31, 26, 2);
030:     MainActivity.addGameResultRecord (cr, 11, 16, 23, 26, 2);
031:     
032:     // 3. Spieltag
033:     MainActivity.addGameResultRecord (cr, 4, 10, 29, 22, 3);
034:     MainActivity.addGameResultRecord (cr, 12, 16, 28, 25, 3);
035:     MainActivity.addGameResultRecord (cr, 7, 2, 19, 20, 3);
036:     MainActivity.addGameResultRecord (cr, 8, 11, 35, 28, 3);
037:     MainActivity.addGameResultRecord (cr, 13, 6, 24, 25, 3);
038:     MainActivity.addGameResultRecord (cr, 9, 18, 30, 24, 3);
039:     MainActivity.addGameResultRecord (cr, 17, 19, 37, 30, 3);
040:     MainActivity.addGameResultRecord (cr, 3, 15, 23, 22, 3);
041:     MainActivity.addGameResultRecord (cr, 1, 14, 35, 18, 3);
042:     MainActivity.addGameResultRecord (cr, 16, 5, 30, 21, 3);
043:     
044:     // 4. Spieltag
045:     MainActivity.addGameResultRecord (cr, 11, 4, 26, 31, 4);
046:     MainActivity.addGameResultRecord (cr, 15, 2, 29, 32, 4);
047:     MainActivity.addGameResultRecord (cr, 1, 7, 28, 26, 4);
048:     MainActivity.addGameResultRecord (cr, 14, 13, 25, 25, 4);
049:     MainActivity.addGameResultRecord (cr, 19, 16, 27, 23, 4);
050:     MainActivity.addGameResultRecord (cr, 5, 9, 30, 27, 4);
051:     MainActivity.addGameResultRecord (cr, 12, 11, 23, 23, 4);
052:     MainActivity.addGameResultRecord (cr, 18, 8, 27, 24, 4);
053:     MainActivity.addGameResultRecord (cr, 10, 17, 35, 30, 4);
054:     MainActivity.addGameResultRecord (cr, 6, 3, 32, 32, 4);
055:     
056:     // 5. Spieltag
057:     MainActivity.addGameResultRecord (cr, 7, 15, 28, 31, 5);
058:     MainActivity.addGameResultRecord (cr, 13, 1, 24, 23, 5);
059:     MainActivity.addGameResultRecord (cr, 9, 19, 35, 29, 5);
060:     MainActivity.addGameResultRecord (cr, 17, 11, 27, 35, 5);
061:     MainActivity.addGameResultRecord (cr, 3, 14, 25, 21, 5);
062:     MainActivity.addGameResultRecord (cr, 8, 6, 24, 28, 5);
063:     MainActivity.addGameResultRecord (cr, 16, 18, 33, 26, 5);
064:     MainActivity.addGameResultRecord (cr, 2, 10, 32, 23, 5);
065:     MainActivity.addGameResultRecord (cr, 5, 12, 30, 23, 5);
066:     
067:     // 6. Spieltag
068:     MainActivity.addGameResultRecord (cr, 1, 3, 26, 20, 6);
069:     MainActivity.addGameResultRecord (cr, 19, 2, 25, 37, 6);
070:     MainActivity.addGameResultRecord (cr, 6, 4, 29, 26, 6);
071:     MainActivity.addGameResultRecord (cr, 10, 15, 28, 28, 6);
072:     MainActivity.addGameResultRecord (cr, 13, 16, 32, 30, 6);
073:     MainActivity.addGameResultRecord (cr, 11, 7, 28, 34, 6);
074:     MainActivity.addGameResultRecord (cr, 14, 9, 25, 24, 6);
075:     MainActivity.addGameResultRecord (cr, 5, 8, 28, 29, 6);
076:     MainActivity.addGameResultRecord (cr, 19, 12, 25, 29, 6);
077:     
078:     // 7. Spieltag
079:     MainActivity.addGameResultRecord (cr, 4, 14, 27, 17, 7);
080:     MainActivity.addGameResultRecord (cr, 15, 11, 22, 28, 7);
081:     MainActivity.addGameResultRecord (cr, 17, 5, 28, 29, 7);
082:     MainActivity.addGameResultRecord (cr, 16, 1, 25, 37, 7);
083:     MainActivity.addGameResultRecord (cr, 7, 10, 31, 30, 7);
084:     MainActivity.addGameResultRecord (cr, 8, 19, 36, 29, 7);
085:     MainActivity.addGameResultRecord (cr, 3, 13, 31, 27, 7);
086:     MainActivity.addGameResultRecord (cr, 9, 6, 28, 27, 7);
087:     MainActivity.addGameResultRecord (cr, 12, 14, 31, 22, 7);
088:     MainActivity.addGameResultRecord (cr, 2, 18, 29, 21, 7);
089:     
090:     // 8. Spieltag
091:     MainActivity.addGameResultRecord (cr, 1, 8, 32, 20, 8);
092:     MainActivity.addGameResultRecord (cr, 6, 7, 25, 26, 8);
093:     MainActivity.addGameResultRecord (cr, 5, 2, 27, 38, 8);
094:     MainActivity.addGameResultRecord (cr, 14, 17, 31, 25, 8);
095:     MainActivity.addGameResultRecord (cr, 11, 10, 28, 28, 8);
096:     MainActivity.addGameResultRecord (cr, 8, 12, 29, 23, 8);
097:     MainActivity.addGameResultRecord (cr, 3, 16, 29, 28, 8);
098:     MainActivity.addGameResultRecord (cr, 19, 4, 30, 38, 8);
099:     MainActivity.addGameResultRecord (cr, 13, 9, 28, 26, 8);
100:     MainActivity.addGameResultRecord (cr, 18, 15, 23, 29, 8);
101:     
102:     // 9. Spieltag
103:     MainActivity.addGameResultRecord (cr, 9, 1, 27, 32, 9);
104:     MainActivity.addGameResultRecord (cr, 7, 18, 33, 25, 9);
105:     MainActivity.addGameResultRecord (cr, 2, 11, 24, 21, 9);
106:     MainActivity.addGameResultRecord (cr, 10, 19, 33, 25, 9);
107:     MainActivity.addGameResultRecord (cr, 12, 1, 27, 32, 9);
108:     MainActivity.addGameResultRecord (cr, 15, 5, 24, 24, 9);
109:     MainActivity.addGameResultRecord (cr, 17, 6, 23, 30, 9);
110:     MainActivity.addGameResultRecord (cr, 4, 13, 40, 27, 9);
111:     MainActivity.addGameResultRecord (cr, 8, 3, 29, 29, 9);
112:     MainActivity.addGameResultRecord (cr, 16, 14, 29, 25, 9);
113:     
114:     // 10. Spieltag
115:     MainActivity.addGameResultRecord (cr, 12, 2, 22, 21, 10);    
116:     MainActivity.addGameResultRecord (cr, 1, 2, 28, 29, 10);
117:     MainActivity.addGameResultRecord (cr, 13, 17, 31, 30, 10);
118:     MainActivity.addGameResultRecord (cr, 14, 8, 32, 29, 10);
119:     MainActivity.addGameResultRecord (cr, 3, 4, 26, 34, 10);
120:     MainActivity.addGameResultRecord (cr, 18, 11, 25, 40, 10);
121:     MainActivity.addGameResultRecord (cr, 19, 7, 28, 34, 10);
122:     MainActivity.addGameResultRecord (cr, 16, 9, 25, 28, 10);
123:     MainActivity.addGameResultRecord (cr, 5, 10, 27, 24, 10);
124:     MainActivity.addGameResultRecord (cr, 6, 15, 33, 30, 10);
125:     
126:     // 11. Spieltag
127:     MainActivity.addGameResultRecord (cr, 12, 7, 22, 21, 11);
128:     MainActivity.addGameResultRecord (cr, 15, 19, 26, 29, 11);
129:     MainActivity.addGameResultRecord (cr, 9, 3, 27, 31, 11);
130:     MainActivity.addGameResultRecord (cr, 17, 16, 38, 21, 11);
131:     MainActivity.addGameResultRecord (cr, 11, 5, 26, 35, 11);
132:     MainActivity.addGameResultRecord (cr, 2, 6, 34, 22, 11);
133:     MainActivity.addGameResultRecord (cr, 10, 18, 31, 25, 11);
134:     MainActivity.addGameResultRecord (cr, 7, 14, 34, 24, 11);
135:     MainActivity.addGameResultRecord (cr, 8, 13, 39, 31, 11);
136:     MainActivity.addGameResultRecord (cr, 4, 1, 26, 29, 11);
137:        
138:     // 12. Spieltag
139:     MainActivity.addGameResultRecord (cr, 16, 4, 22, 31, 12);
140:     MainActivity.addGameResultRecord (cr, 14, 15, 22, 20, 12);
141:     MainActivity.addGameResultRecord (cr, 1, 17, 35, 34, 12);
142:     MainActivity.addGameResultRecord (cr, 13, 2, 20, 32, 12);
143:     MainActivity.addGameResultRecord (cr, 19, 11, 24, 32, 12);
144:     MainActivity.addGameResultRecord (cr, 3, 7, 26, 23, 12);
145:     MainActivity.addGameResultRecord (cr, 9, 8, 29, 29, 12);
146:     MainActivity.addGameResultRecord (cr, 5, 18, 30, 24, 12);
147:     MainActivity.addGameResultRecord (cr, 6, 10, 37, 33, 12);
148:     MainActivity.addGameResultRecord (cr, 13, 12, 22, 18, 12);
149:     
150:     // 13. Spieltag
151:     MainActivity.addGameResultRecord (cr, 17, 3, 22, 26, 13);
152:     MainActivity.addGameResultRecord (cr, 4, 9, 32, 26, 13);
153:     MainActivity.addGameResultRecord (cr, 2, 14, 23, 22, 13);
154:     
155:     // 15. Spieltag
156:     MainActivity.addGameResultRecord (cr, 5, 14, 26, 26, 15);
157:     
158:     // 18. Spieltag
159:     MainActivity.addGameResultRecord (cr, 18, 12, 28, 27, 18);
160:     
161:     this.textViewStatus.setText("Done: 122 games added !");
162: }