Če želimo pošiljati sporočila, jih moramo nekam vpisati, zato si pripravimo nov zavihek, na katerem bo vnosno polje za naše sporočilo. Doslej smo napravili že tri zavihke, zato nam dodajanje novega ne bi smelo delati težav.

Najprej ustvarimo nov razred, ki bo predstavljal aktivnost tega zavihka. V Idei z desnim gumbom kliknemo na paket src/com.example in izberemo New > Android Component. Pri Name vpišemo SendMessageActivity, kind pa pustimo na Activity. V naslednjem koraku v mapi res/layout ustvarimo novo postavitev send_message.xml, v katero vpišemo naslednjo kodo:

<?xml version="1.0" encoding="utf-8"?>
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:stretchColumns="1"
style="@style/main_window">
<TableRow>
<TextView style="@style/PromptText"
android:text="@string/insert_message"/>
<EditText android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="@+id/message_input"
android:lines="5"
android:singleLine="false"
android:inputType="textMultiLine"
android:gravity="top|left"/>
</TableRow>
<TableRow>
<TextView style="@style/border_row"/>
</TableRow>
<TableRow>
<Button android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/send_text"
android:id="@+id/send_button"
style="@style/nice_button"/>
</TableRow>
</TableLayout>

Ta postavitev je podobna postavitvi v datoteki main.xml, ki smo jo že razložili, nove pa so štiri lastnosti značke EditText. android:lines="5" pove, da naj se komponenta za vnos razteza čez pet (vnosnih) vrstic, android:singleLine="false" pomaga, da se prejšnja lastnost upošteva, android:inputType="textMultiLine" omogoči prilagoditev tipkovnice naprave za vnos večvrstičnega besedila, lastnost android:gravity="top|left" pa bo vneseno besedilo poravnala v zgornji levi kot vnosnega polja. Ne pozabimo še v datoteko strings.xml dodati besedila:

<string name="insert_message">Napiši\nsporočilo</string>
<string name="send_text">Pošlji</string>

Nato odpremo datoteko SendMessageActivity.java in povozimo metodo onCreate, da bo pogled uporabil našo postavitev:

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.send_message);
}

Vsebino zavihka smo tako ustvarili, zdaj pa ga moramo še dodati med preostale. Za to odpremo datoteko MojMikroAndroidActivity.java in na konec metode onCreate dodamo kodo za dodatni zavihek:

tabHost.addTab(tabHost.newTabSpec("send_message_tab")
.setIndicator(getString(R.string.send_message_text),
getResources().getDrawable(R.drawable.ic_tab_map))
.setContent(new Intent(this, SendMessageActivity.class)));

V datoteko strings.xml dopišemo še <string name="send_message_text">Pošlji</string> in zavihek je že prisoten v naši aplikaciji.

Preprosta stran za vnos in pošiljanje sporočila

Nastavitev strežnika

Podatke pošiljamo na strežnik iz več vzrokov: da jih lahko delimo z drugimi uporabniki, da so shranjeni na oddaljeni, varni lokaciji, da lahko do njih dostopamo povsod, kjer imamo dostop do interneta. Da lahko iz Android aplikacije pošljemo podatke na oddaljen strežnik, pa moramo pravilno nastaviti tako aplikacijo kot tudi strežnik. Android nima podpore za branje in pisanje v oddaljene podatkovne baze, zato moramo za ta postopek uporabiti kar navaden http postopek, prek katerega pošiljamo podatke v navadni besedilni obliki, na strežniški strani pa jih prestrežemo in shranimo na primer s PHP-jem, ki ga podpira večina strežnikov. Za obliko zapisa podatkov je priporočljivo uporabiti JSON, ki ga podpirata tako PHP kot Android. Na strežniški strani potrebujemo dve php datoteki. Za zapis podatkov potrebujemo datoteko
write.php:

<?php
$con = mysql_connect("<strežnik>","<up. ime>","<geslo>");
if (!$con) {
die('Could not connect: ' . mysql_error());
}
mysql_select_db("<baza>", $con);
mysql_set_charset('utf8',$con);
$author = urldecode($_POST['author']);
$message = urldecode($_POST['message']);
$date = date('Y-m-d H:i:s', time());
$longitude = $_POST['longitude'];
$latitude = $_POST['latitude'];
$query = "INSERT INTO mma_message (author, message, date, longitude, latitude) " .
"VALUES ('".$author."', '".$message."', '".$date."', '".$longitude."', '".$latitude."')";
$result = mysql_query($query);
echo $result;
mysql_close($con);
?>

Ta skripta prebere podatke o novem sporočilu iz http zahtevka in jih zapiše v bazo. Za branje podatkov iz baze potrebujemo datoteko read.php:

<?php
$con = mysql_connect("<strežnik>","<up. ime>","<geslo>");
if (!$con) {
die('Could not connect: ' . mysql_error());
}
mysql_select_db("<baza>", $con);
mysql_set_charset('utf8',$con);
$result = mysql_query("SELECT * FROM mma_message ORDER BY date DESC LIMIT 0,
100");
$rows = array();
while($row = mysql_fetch_assoc($result)) {
$clear_row = array('author'=>$row['author'],
'message'=>$row['message'],
'date'=>$row['date'],
'longitude'=>$row['longitude'],
'latitude'=>$row['latitude']);
array_push($rows, $clear_row);
}
echo json_encode($rows);
mysql_close($con);
?>

Ta skripta prebere zadnjih sto zapisov v bazi, jih pretvori v JSON-besedilo in izpiše. Da skripti delujeta, potrebujemo na strežniku bazo z razpredelnico mma_message, ki ima stolpce author (varchar(100)), message (text), date (datetime), latitude (int) in longitude (int). Kodiranje baze nastavimo na UTF-8, če bomo kdaj hoteli primerjati vsebine, pa na utf8_slovenian_ci. Vsi ti podatki veljajo za MySQL-bazo.

Ti datoteki si moramo napraviti, če želimo imeti lastni strežnik, v tej Mali šoli pa se bomo vsi povezali na en strežnik, da bomo lahko izmenjali izkušnje. Datoteki za branje sta pripravljeni na naslovih: http://logica.pro/read.php in http://logica.pro/write.php.

Pošiljanje na strežnik

Kot smo že omenili, Android podpira prejemanje in pošiljanje podatkov prek http postopka. Uporabimo ga tako, da ustvarimo razred MessagesConnection.java:

public class MessagesConnection {
protected String readFromServer(Map<String, String> parameters, String link) {
try {
//pošiljanje podatkov
HttpPost post = new HttpPost(link);
if (parameters != null && parameters.size() > 0) {
post.setEntity(new UrlEncodedFormEntity(convertParameters(parameters)));
}
HttpClient client = new DefaultHttpClient();
HttpResponse httpResponse = client.execute(post);
//prejemanje podatkov
HttpEntity entity = httpResponse.getEntity();
InputStream inputStream = entity.getContent();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, "UTF-
8"));
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
reader.close();
return sb.toString();
} catch (IOException e) {
return null;
}
}
private List<NameValuePair> convertParameters(Map<String, String> parameters) {
List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>();
for (String name : parameters.keySet()) {
String value = parameters.get(name);
nameValuePairs.add(new BasicNameValuePair(name, value));
}
return nameValuePairs;
}
}

Metoda readFromServer ustvari povezavo do strežnika, pošlje nanj podatke (s pomočjo metode convertParameters), nato pa prejme rezultat in ga vrne kot besedilo. Za pošiljanje sporočil na strežnik zdaj napišimo podrazred razreda MessagesConnection.java:

public class MessageSender extends MessagesConnection {
private static final String url = "http://logica.pro/write.php";
public static final MessageSender INSTANCE = new MessageSender();
private MessageSender() { }
public boolean sendMessage(String author, String message, int longitude, int latitude) {
Map<String, String> parameters = new HashMap<String, String>(4);
try {
parameters.put("author", URLEncoder.encode(author, "UTF-8"));
parameters.put("message", URLEncoder.encode(message, "UTF-8"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
parameters.put("longitude", String.valueOf(longitude));
parameters.put("latitude", String.valueOf(latitude));
String response = readFromServer(parameters, url);
return "1".equals(response);
}
}

Ta razred bo napravil seznam parametrov sporočila, ki ga želimo poslati, nato pa ga s pomočjo nadrazreda poslal na naslov http://logica.pro/write.php. Zdaj moramo metodo sendMessage le še poklicati, ko uporabnik pritisne gumb Pošlji, ki smo ga napravili v poglavju Nov zavihek.

Odprimo torej razred SendMessageActivity.java in mu dodajmo metodo:

private void sendData() {
SharedPreferences pref = getSharedPreferences("mma_preferences", 0);
String name = pref.getString("name", "");
if (name == null || name.length() == 0) {
Toast.makeText(this, "Najprej vpiši svoje ime.", Toast.LENGTH_SHORT).show();
return;
}
TextView messageTextView = (TextView) findViewById(R.id.message_input);
String message = messageTextView.getText().toString();
if (currentLocation == null) {
Toast.makeText(this, "Čakam na GPS lokacijo...", Toast.LENGTH_SHORT).show();
return;
}
boolean success = MessageSender.INSTANCE.sendMessage(name, message,
(int) (currentLocation.getLongitude()*1000000), (int) (currentLocation.getLatitude()*1000000));
if (success) {
Toast.makeText(this, "Sporočilo je poslano.", Toast.LENGTH_SHORT).show();
messageTextView.setText("");
} else {
Toast.makeText(this, "Sporočilo ni poslano.", Toast.LENGTH_SHORT).show();
}
}

Ta metoda najprej prebere ime iz shranjenih nastavitev, če to ni opredeljeno, pa na to opozori uporabnika. Da lahko uporabljamo shranjene nastavitve med različnimi aktivnostmi, jih moramo shraniti v deljeni pomnilnik. Za to moramo v razredu MyActivity.java zamenjati kodo getPreferences(0) z getSharedPreferences("mma_preferences", 0) in ponovno vnesti podatke.

Nato metoda prebere še sporočilo, GPS-podatke (več o tem v naslednjem poglavju) in vse skupaj pošlje prek metode sendMessage. Ko je vse to opravljeno, še obvesti uporabnika o uspešno opravljeni nalogi.

Zdaj moramo le še povezati to metodo z gumbom Pošlji. To storimo tako, da na konec metode onCreate dodamo akcijo gumba, ki pokliče metodo sendData:

findViewById(R.id.send_button).setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
sendData();
}
});

Pridobivanje lokacije

GPS-lokacijo v Androidu preberemo tako, da napravimo poslušalca (listener), ki posluša, kdaj GPS-senzor objavi, da se je lokacija spremenila. Ko zazna spremembo (prvo ali novo lokacijo), nam to posreduje. Mi bomo za poslušalca napravili kar razred SendMessageActivity, ki bo sam sprejel in obdelal podatke o novi lokaciji. Odprimo torej ta razred in mu določimo, da implementira LocationListener, dodajmo pa mu tudi polje Location, v katerem bo shranjena trenutna lokacija:

public class SendMessageActivity extends Activity implements LocationListener {
private Location currentLocation = null;

Ko to storimo, nam Idea sporoči napako, da manjkajo štiri implementacije metod. Dovolimo ji, da jih ustvari namesto nas (Alt+Enter > Implement Methods). Zanima nas samo metoda onLocationChanged, v kateri shranimo novo lokacijo, ostale pa pustimo prazne:

@Override
public void onLocationChanged(Location location) {
currentLocation = location;
}
@Override
public void onStatusChanged(String s, int i, Bundle bundle) { }
@Override
public void onProviderEnabled(String s) { }
@Override
public void onProviderDisabled(String s) { }

Tako je naš razred postal poslušalec lokacijskih podatkov, treba pa mu je samo še povedati, kaj točno naj posluša. Za to poskrbi naslednja koda, ki jo dodamo na konec metode onCreate:

LocationManager locationManager = (LocationManager)
getSystemService(Context.LOCATION_SERVICE);
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, this);

S tem bo naš razred začel poslušati spremembo lokacijskih podatkov in vsako novo lokacijo bo shranil v polje currentLocation. Iz tega polja pa smo že v prejšnjem poglavju prebrali zemljepisno širino in dolžino ter ju poslali skupaj s sporočilom. Zdaj lahko poženemo aplikacijo na naši napravi in pošljemo sporočilo na strežnik. Rezultate lahko vidimo na spletni strani http://logica.pro/mojmikro/zemljevid.html.

Branje s strežnika

Sporočilo smo uspešno poslali na skupni strežnik, zdaj pa bi želeli prebrati še vsa sporočila, ki so jih poslali tudi drugi uporabniki aplikacije. Za to moramo najprej napraviti nov podrazred razreda MessagesConnection:

public class MessagesRetriever extends MessagesConnection {
public static final MessagesRetriever INSTANCE = new MessagesRetriever();
private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd
HH:mm:ss");
private static final String READ_LINK = "http://logica.pro/read.php";
private MessagesRetriever() { }
public List<Message> readMessages() {
String response = readFromServer(null, READ_LINK);
if (response != null && response.length() > 0) {
return parseResponse(response);
} else {
return new ArrayList<Message>();
}
}
private List<Message> parseResponse(String objectsString) {
List<Message> messages = new ArrayList<Message>();
try {
JSONArray jsonArray = new JSONArray(objectsString);
for (int i = 0; i < jsonArray.length(); i++) {
JSONObject data = jsonArray.getJSONObject(i);
String author = data.getString("author");
String messageString = data.getString("message");
Date date = DATE_FORMAT.parse(data.getString("date"));
int longitude = data.getInt("longitude");
int latitude = data.getInt("latitude");
Message message = new Message(author, date, latitude, longitude, messageString);
messages.add(message);
}
} catch (JSONException e) {
e.printStackTrace();
} catch (ParseException e) {
e.printStackTrace();
}
return messages;
}
}

Ta razred ima samo eno javno metodo, ki pošlje zahtevek brez parametrov na stran http:// logica.pro/read.php, od koder prebere sporočila v obliki zapisa JSON. Metoda parseResponse nato prevede ta zapis v danost Message in vrne seznam sporočil. Svež seznam sporočil lahko zdaj dobimo tako, da pokličemo kodo MessagesRetriever.INSTANCE.readMessages().

Prikaz sporočil na zemljevidu

Za prikaz sporočil na zemljevidu ne potrebujemo veliko dela, saj smo v prejšnjem delu že vse pripravili. Odprimo razred MessagesMapActivity.java in metodi reloadMarkers dodajmo naslednjo kodo:

for (Message message : MessagesRetriever.INSTANCE.readMessages()) {
d ialogOverlay.addItem(message.getLatitude(), message.getLongitude(),
MessageFormat.format("{0} - {1}", message.getAuthor(),
DATE_FORMAT.format(message.getDate())),
message.getMessage());
}

Ta koda bo najprej prebrala vsa sporočila s strežnika, nato pa vsakega posebej dodala na zemljevid enako, kot smo to storili z besedilnima sporočiloma (ki ju lahko zdaj izbrišemo). Če poženemo aplikacijo, lahko vidimo naša sporočila na zemljevidu.

Na zemljevidu se prikažejo točke. Če kliknemo nanje, se prikaže sporočilo.

Prikaz sporočil v seznamu

Na zavihku Regije trenutno prikazujemo seznam regij Slovenije brez kakšnega večjega smisla. Zamenjajmo raje ta seznam za seznam vseh sporočil. Za to moramo odpreti razred RegionsListActivity in mu dodati metodo, ki pripravi seznam besedil, ki predstavljajo sporočila:

private List<String> getMessages() {
List<String> messages = new ArrayList<String>();
for (Message message : MessagesRetriever.INSTANCE.readMessages()) {
messages.add(MessageFormat.format("{0} - {1}\n{2}",
message.getAuthor(), DATE_FORMAT.format(message.getDate()), message.getMessage()
));
}
return messages;
}

Ta koda prebere sporočila s strežnika in jih pretvori v besedilo. Nato moramo kodo le še vstaviti namesto seznama regij:

setListAdapter(new ArrayAdapter<String>(this,
R.layout.regions_list_item,
getMessages()));

Dobro bi bilo le še preimenovati zavihek Regije v na primer Sporočila, pa tudi razred RegionsListActivity zdaj ni več najbolj jasno poimenovan. Ne glede na to pa nam drugi zavihek prikaže seznam vseh sporočil, če ga odpremo.

Seznam zadnjih sto sporočil, urejen po datumu od najnovejšega do najstarejšega.

Zaključek

Prišli smo do točke, ko je naša aplikacija dobila namembnost – postala je namreč povsem funkcionalna aplikacija za pošiljanje in sprejemanje sporočil. Opazili boste, da je prehajanje med zavihki postalo počasno, to pa zato, ker se zdaj pred prikazom zavihka preberejo podatki s strežnika. Za to bomo poskrbeli v naslednjem delu, ko bomo priredili aplikacijo, da bo podatke brala v ozadju in nam ne bo treba čakati nanje. Do takrat pa ste vabljeni na spletno stran www.logica.pro/mojmikro, kjer najdete izvorno kodo tega prispevka, lahko pa tudi kaj vprašate, komentirate, predlagate ali se pohvalite s svojimi izdelki.

Sicer pa veselo uporabljajte našo novo aplikacijo in ne pozabite poslati pozdravov s počitnic.


Aplikacijo lahko zelo preprosto priredimo za povsem zasebno sporočanje. Treba je le postaviti PHP+MySQL-strežnik, nanj skopirati datoteki read.php in write.php ter popraviti url-ja v razredih MessageSender.java in MessagesRetriever.java. In že se lahko e-sporazumevate, ne da bi vas skrbelo, da bo kakšna velika organizacija prodajala vaše podatke.

Moj mikro, Julij Avgust 2012 | Rok Končina