13. lecke Üzenőfal (1)

Online Android szakkör (DKRMG)


Elérkeztünk az Android szakkör utolsó alkalmazásához. Erre az neves alkalomra egy különleges programot tartogattunk: két leckében elkészítünk egy közös üzenőfal alkalmazást. Az üzenőfalunk egy webes szolgáltatás lesz, ahová az okostelefonunkról küldhetünk üzeneteket, illetve kilistázhatjuk a korábban általunk és mások által írt bejegyzéseket.

A feladatot annyival megkönnyítettük, hogy az eddig beérkezett üzeneteket megtekinthetitek a szakkör honlapján ezen a linken.

A mai lecke végén első nekifutásra a programunk csak a saját üzeneteinket fogja kilistázni, viszont már képesek leszünk új üzeneteket küldeni a közös honlapra. A következő (utolsó) leckében már a teljes előzmény megjelenik az okostelefonunk kijelzőjén, és a program hibatűrésén is javítunk egy kicsit.

Első nekifutás

Jó programozóként nem próbálunk ajtóstul rontani be a házba, a feladatot több lépésben oldjuk meg. Az első nekifutás remélhetőleg sokak számára ismerősnek tűnik majd; az alábbi lépések mindegyikét elvégeztük már a 8. leckében is.

A cél, hogy a telefon képernyőjén megjelenjen egy szövegmező, egy gomb és egy lista. A gomb lenyomásakor a szövegmezőben tartalmat egy üzenetté alakítjuk át, és gyorsan beillesztjük a listába. Ehhez szükségünk lesz egy egyszerű felhasználói felületre, egy Uzenet osztályra, és némi kódra, ami beilleszti az új elemeket, majd egy adapter segítségével meg is jeleníti őket.

Új projekt

Hozzunk létre egy új projektet Uzenofal néven. Ilyet már rengetegszer csináltunk. Ha mégis bizonytalan volnál a pontos lépésekben, akkor pl. a 7. lecke leírásából lehet puskázni!

Layout - felhasználói felület

Készítsük el a fenti felhasználói felületet.

A fenti felhasználói felület természetesen csupán egy egyszerű vázlat. A korábbi leckékhez hasonlóan javasolnánk, hogy csinosítsátok és színesítsétek ki!

Uzenet osztály

Emlékezhetünk a szótárfüzet programból, hogy amikor komplexebb, összetettebb adatot kell ábrázolnunk egy egyszerű számnál vagy szövegnél, akkor gyakran saját osztályokra kell hagyatkoznunk. Ha egy egész számot akarunk tárolni, arra ott az int típus. Szövegekhez használhatunk Stringeket. Azonban a Java nyelv ahogy magától azt sem tudja, hogy a két nyelven jegyzett Szo osztályt hogyan kezelje, úgy az üzenetek tárolásához is egy kis segítséget kell neki adnunk.

Ha az üzenetünk egy egyszerű szöveg lenne, használhatnánk itt is a String típust. Mi viszont szeretnénk tudni, hogy ki, mikor és mit írt.

Az osztály megírásával létre hozunk a Java nyelvben egy új típust; felvázoljuk a változóink tervrajzát, hogy az Uzenet típusú változóknak milyen adatokat kell tárolnia, illetve milyen belső függvényekkel rendelkezik.

Mi legyen az Uzenet osztályban

Az, hogy az üzeneteink nem egyszerű típusok, az egyértelműen látható az üzenőfal honlapján is.

Ahogy azt írtuk, az üzeneteink három fontos dolgot tartalmaznak: ki, mikor és mit írt. Ezeken túl még egy azonosítót is adunk az üzenetünknek, ami a közös üzenőfalhoz lesz majd elengedhetetlen.

  1. public String felado; — Ki küldte?

  2. public String szoveg; — Mit küldött?

  3. public Date __createdAt; — Mikor küldte?

  4. public String id; — egy azonosító.

Ahhoz, hogy az üzenőfalat közösen tudjuk használni, fontos, hogy a fenti neveket pontosan ugyanígy használja mindenki! Az id tulajdonságra is csak a közös web szolgáltatás miatt lesz szükségünk.

Osztály létrehozása

A 8. leckéhez hasonlóan hozzunk létre egy új Java osztályt (class), ezúttal Uzenet néven!

Az Uzenet osztályba írjuk be a fenti mezőket/változókat.

Amikor a public Date __createdAt; sort beírod, a Date szócskát bepirosítja, és valószínűleg megjelenik egy kis buborék is felette. Ettől nem kell megijedni, de érdemes megérteni, hogy miért történik.

A Date egy ugyan olyan típus, mint a saját Uzenet, vagy korábban a Szo típusunk, vagy a beépített EditText, Button, stb. A Date egy dátumot (és időpontot) képes tárolni és pár dolgot csinálni vele. A probléma az, hogy az AndroidStudio két ilyen nevű típust ismer és nem tudja, hogy te melyiket szeretnéd használni. Az egyik a java.util csomagban lévő Date (teljes neve: java.util.Date), mi ezt fogjuk használni, és általában mindig mindenki ezt használja. De bizonyos speciális helyzetekben szükség lehet a másikra is: a java.sql csomagban lévő Date típusra (teljes nevét biztos kitaláltátok már, de álljon itt a teljesség kedvéért: java.sql.Date).

Már csak az a kérdés, hogy hogyan mondjuk meg, hogy melyiket szeretnénk használni. Ez egyszerű:
Kattints rá a piros Date szóra, nyomd meg az Alt+Enter billentyűkombinációt, válaszd ki a java.util.Date sort és kész is!

Le is ellenőrizheted: A fájl legtetején a package ... sor alatt megjelent egy import java.util.Date; sor is, ezzel jelezve, hogy ebben a fájlban a Date típus melyiket jelenti pontosan.

Végezetül még mindig az Uzenet osztályon belül hozzunk létre egy toString() függvényt is. Ez a függvény mindig hasznos segítőtársunk, és az üzenet listában való megjelenítésekor is szükségünk lehet rá.

Például:


@Override
public String toString() {
    return felado + " írta ekkor: " + __createdAt.toString() + ":\n " + szoveg;
}

Ha elakadtatok volna, vagy ellenőrzésképp kattintás után láthatod a kész osztályt. De arra kérünk, hogy tényleg csak akkor nézd meg, ha már magadtól, kézzel megpróbálkoztál elkészíteni, úgy tudod csak megtanulni!

ujUzenet.szoveg = szovegdoboz.getText().toString();

Lista létrehozása (kód)

Nem győzzük felemlegetni a 8-9. leckét, ahol ezeket a lépéseket mind elvégeztük már egyszer. Emlékeztetésként: a ListView komponensünk önmagában nem sokat ér, kell alá raknunk egy ArrayList<Uzenet> változót, ami az üzeneteink listáját tárolja. A rendszerünk úgy lesz teljes, ha a változót és a layout komponenst egy adapterrel össze is kapcsoljuk!

  1. Hozz létre a MainActivityben egy új változót: ArrayList<Uzenet> uzenetek; Ez a változó képes üzenetek listáját tárolni.

  2. Az onCreate függvényünk adjon értéket a változónknak. Ez legyen egy új üres lista: uzenetek = new ArrayList<Uzenet>();

  3. Ha szeretnénk, akkor még az onCreate függvény végén adjunk hozzá az üzenetek listájához egy új elemet, amolyan üdvözlésként. Szegény felhasználót mégse egy üres lista fogadja! Hozz létre egy új üzenetet, majd add hozzá a listához az uzenetek.add belső függvénnyel.

    
    Uzenet udv = new Uzenet();      // létrehoz egy új Uzenet típusú objektumot
    udv.felado = "Én";              // Beállítja a feladóját
    udv.szoveg = "Szia!";           // és a szövegét
    udv.__createdAt = new Date();   // Valamint a létrehozási idejét beállítja a jelenlegi (amikor a kód fut) időpontra
    uzenetek.add(udv);              // végül hozzáadjuk a listához
    

Adapter

Amint arra korábban is utaltunk, a ListView komponenst és az ArrayList változónkat össze is kell kapcsolni valamilyen módon. Erre használtuk a korábbi leckékben az adaptereket.

Az egyszerű megoldás:

  1. Hozzunk létre egy új változót az Activityben.

    ArrayAdapter<Uzenet> adapter;
  2. Az onCreate függvényben adjunk értéket az adapternek

    adapter = new ArrayAdapter<Uzenet>(this, android.R.layout.simple_list_item_1, uzenetek);
  3. Az onCreate függvény végén állítsuk be, hogy a ListView komponensünk az új adapter használja.

    Ha a ListView komponensre mutató változónk még nincs, akkor hozzuk létre, és adjunk neki értéket a szokásos módon a findViewById(R.id...) segítségével

    lista.setAdapter(adapter);
  4. A listánk attól lesz igazán hasznos, hogy gombnyomásra új elemeket illeszthetünk a végére. Írj egy onClick függvényt a gombnak, ami lenyomáskor – az onCreate-ben megírt üdvözléshez hasonlóan – létrehoz egy új üzenetet, és a fentiekhez hasonlóan beilleszti a listába! Az üzenet feladója legyen a te neved (vagy legalábbis legyen némileg ötletesebb, mint az "Én" szócska), a szövege származzon a szövegmezőből (), a __createdAt pedig az onCreate-beli kódhoz hasonlan legyen new Date();. Ez biztosítja, hogy a __createdAt értéke a jelenlegi dátum/idő legyen.

    ujUzenet.szoveg = szovegdoboz.getText().toString();

    Feltéve, hogy az EditText-hez tartozó változónak szovegdoboz a neve.

    Az üzenet hozzáadása után ne felejtsünk el szólni az adapternek: frissítse a listát

    adapter.notifyDataSetChanged();

    Végül ne felejtsük el kitörölni a szövegmező tartalmát a setText(""); segítségével.

A fenti megoldás az Uzenet osztály toString függvényét használva egy egyszerű szöveggé alakítja az üzeneteket. Ennél láttunk már sokkal elegánsabb lehetőséget is a 11. lecke szorgalmijai között, ahol minden lista elem kap egy saját kis layoutot. Itt találod. Javasolnánk, hogy alakítsd át a listádat az ott leírt módon!

Most futtasd a programodat, és győződj meg róla, hogy gombnyomásra hozzá tudsz adni új elemeket a listához.

Közös üzenőfal

Fontos megjegyezni, hogy a lecke folytatásának a sikeres teljesítéséhez (illetve az elkészült program használatához) szükség lesz a telefonon internetelérésre. A program működik mobilneten keresztül is, de nagyon változó, hogy éppen mennyi adatot szeretne letölteni, ami lehet sok, így drága is. Ezért azt javasoljuk, hogy mindenképp az otthoni WiFi-re legyetek felcsatlakozva, miközben használjátok a programot! Reméljük, ez nem okoz gondot senkinek.

Eddig a programunk ugyan igen csinos, valójában elég haszontalan. Az általunk írt üzeneteket nem hogy a többi szakkörös nem látja, de még akkor is eltűnnek, ha újraindítjuk a programot, vagy csak egy merész mozdulattal elforgatjuk a telefon kijelzőjét. A szótárfüzet leckében ezen feledékenység orvoslására a program bezárása előtt gyorsan egy külső fájlba mentettük el a szavainkat; azonban itt ez sem megoldás számunkra, hiszen az SD kártyára mentett üzeneteket rajtunk kívül soha senki nem fogja látni. Ahhoz, hogy közös üzenőfalunk legyen, szükségünk van egy közös pontra az interneten, ahol összegyűlnek az üzenetek.

Egy újabb API

Az üzenőfal megvalósításához az Azure Mobile App szolgáltatást használjuk. Amikor a lecke ezen sorait olvassátok, már az interneten vár rátok egy kész üzenőfal szolgáltatás, ami a megfelelő kérésekre válaszolva képes a fenti módon létrehozott üzenetek tárolására, illetve a korábbi üzenetek lehívására. Az adatbázisunk jelenleg egy távoli szerveren várakozik. Ahhoz pedig, hogy kapcsolatba léphessünk vele az okostelefonunkon keresztül, meg kell gyorsan ismerkednünk egy újabb API-val: az Azure Mobile Services API-val.

Az Open Street Map térkép API-hoz hasonlóan ez is egy külső Java könyvtár, ami nagyban leegyszerűsíti az életünket. A lecke végéig láthatjuk, hogy csupán pár sor programmal hozzá is adhatunk egy új üzenetet az adatbázishoz - ez a művelet egyébként a háttérben olyan összetett hálózati hókusz-pókuszt rejt, amitől az olvasót szeretnénk megkímélni. Az Azure Mobile App szolgáltatásairól ebben a leckében nem fogunk részletesen beszélni, kizárólag az API használatát tűzzük ki célunknak. Az itt előkerülő munka pedig kísértetiesen fog hasonlítani az előző (12.) leckében látottakhoz.

Külső API hozzáadása

Az Azure Mobile App Service használatához jeleznünk kell az új külső kódcsomagot (függőséget)

Ehhez nyissuk meg a build.gradle fájlunkat, és nézzük meg a dependecies részt.

Emlékeztetésként, a gradle fájl dependencies részlete tartalmazza azon külső kódcsomagok listáját, amik nem részei se az Android rendszernek, se a Java nyelvnek. Ide kell hozzáadnunk az Azure Mobile App Service-t is, ezáltal jelezve az Android Studio-nak, hogy mostantól tekintsen úgy az Azure API-ra, mint bármilyen más belső kódra.

Ehhez másoljuk be az alábbi sort a dependencies blokk végére:

compile 'com.microsoft.azure:azure-mobile-services-android-sdk:2.0.2+'

Most pedig kattintsunk a sarokban felugró Sync now feliratra!

Ha valami oknál fogva a Sync now sáv nem ugrik fel magától, akkor sem történik semmi probléma. A gradle fájl változtatása után nyojuk meg a fenti sávban található Sync Project with Gradle Files gombot (lenti képen pirossal keretezve).

Engedélyek

Emlékeztetőként nézd meg a gyorssegély lapot!

Az alkalmazásunknak egyetlen különleges engedélyre lesz szüksége: az üzenetek küldéséhez és lehívásához csatlakoznunk kell az internetre.

Az alkalmazásunk a szükséges engedélyek listáját az AndroidManifest.xml fájlban tárolja.

  1. Nyissuk meg a manifeszt fájlt

  2. Adjuk hozzá a szükséges engedélyt az application sor fölé

    <uses-permission android:name="android.permission.INTERNET"/>

Néhány új változó

A térképpel szemben az Azure API nem ad új látványos layout komponenseket. A kapcsolatot az adatbázissal a Java kódból néhány változónkon keresztül fogjuk tartani. Ehhez hozzuk létre az alábbi két változót az Activityben:


private MobileServiceClient azureClient;
private MobileServiceTable<Uzenet> kozosUzenetek; //az uzenetek adattáblája

Értékadás

Az onCreate függvényünkben adjunk is értéket az új változóinknak. Az alábbi pár sor fogja megnyitni a kapcsoaltot az internetes szolgáltatással. A sorok pontos jelentésének részletes magyarázatától eltekintenénk. Nagy vonalaiban azonban látható, hogy létrehozunk egy új MobileServiceClient objektumot, megadva a webes szolgáltatásunk nevét és kulcsát. A try-catch szerkezettel is találkoztunk már egészen pontosan a fájlok megnyitásánál. Ebben az esetben a catch a hibásan beírt URL-t kapja el (a mi általunk használt URL szerencsére hibátlan: https://dkrmg-szakkor-uzenofal.azure-mobile.net/)


try {
    azureClient = new MobileServiceClient(
            "https://dkrmg-szakkor-uzenofal.azure-mobile.net/",
            "kZkkgmDCeseSiEjjJrwRgKKFKNjPAq13",
            this);
} catch (MalformedURLException e) {
    Log.e("AzureClient", "Hibás URL");
}
kozosUzenetek = azureClient.getTable(Uzenet.class);

A kód utolsó sora pedig megkéri a webes szolgáltatást: legyen szíves kapcsolja össze az kozosUzenetek változónkat azzal az adattáblával, amiben az összes közös üzenet lakik. Innentől válik igazán fontossá, hogy az Uzenet osztály mindenkinél pontosan ugyanazokat a mezőket tartalmazza! Erre a kozosUzenetek adattáblára gondolhatunk úgy, mint egy furcsa listára, ami az interneten lakik valahol.

Új üzenet küldése a közös Üzenőfalra

Most pedig villámgyorsan demonstráljuk is, hogy a fenti körülbelül tíz soros előkészületek után mennyire is egyszerűvé válik a közös üzenőfalunk használata. Ehhez egészen pontosan az új üzeneteinket nem csak a saját kis listánkhoz fogjuk hozzáadni, hanem a kozosUzenetekbe is elküldjük.

Ki kell valamit törölni az eddigi kódból?

Egyelőre ne töröljünk ki a korábbi kódból semmit! Ezt az élvezetet hagyjuk meg a következő leckére.

Üzenet küldése

A gombunk lenyomásakor jelenleg létrehozunk egy új Uzenet típusú helyi változót, és gyorsan értéket is adunk a különböző tulajdonságainak (felado, szoveg). Nincs más dolgunk, mint a listához hozzáadás után beillesztjük az alábbi sort:


// feltéve, hogy az új üzenetet az uzenet változóba tettük. Ha nem, akkor ezt át kell írni!
kozosUzenetek.insert(uzenet);

A fenti kód fogja a kozosUzenetek adattáblát és beilleszt egy új elemet. Mint említettük, a kozosUzenetek adattáblára tekinthetünk úgy, mint egy furcsa listára, ami az interneten lakik valahol. A különbség egyelőre annyi, hogy az add(uzenet) parancs helyén az insert(uzenet) áll. Ezzel a beillesztéssel az új üzenetünket hozzáadjuk a közös adatbázishoz.

Próbáld ki!

Miután beírtad a fenti kódot, futtasd a programot, adj hozzá 1-2 üzenetet, és nézd meg a webes felületünkön, hogy megjelenik-e az üzenet.

Szorgalmi feladatok

1*. Üres üzenetet ne küldjünk el!

Jelenleg a Küldés gomb megnyomására az EditText teljes tartalmát, ellenőrzés nélkül elküldjük a szerverre. Akkor is, ha az üres, ez pedig nem szép dolog. Javítsd!

Használhatod a String típus length() belső függvényét, ez a string hosszát adja meg. Vagy használhatod a TextUtils.isEmpty() függvént is, igaz értékű ha nincs a stringben semmi.

(Ha megoldottad, utána mindenképp nézd meg, van benne egy érdekesség!)


// beírt szöveg kiolvasása a messageText változóba

if (TextUtils.isEmpty(messageText)) { // ha üres
    Toast.makeText(this, "Üres üzenetet nem küldünk el.", Toast.LENGTH_SHORT).show(); // feldobunk egy Toast-ot
    Log.w("MainActivity", "Üres üzenet küldése megszakítva."); // és logolunk is
    return; // majd idő előtt kilépünk a függvényből (magyarázat lejjebb)
}

// nem üres --> üzenet létrehozása, hozzáadása, szerverre küldése

Ezt a pár sor kell a gomb onClick függvényének az elejére beszúrni (de már az után, hogy kiolvastad a beírt szöveget az EditTextből).

Nézzük meg jobban: miért nem tesszük az egész üzenet létrehozást és küldést egy nagy if-be?
Igazából annak sem lenne semmi akadálya, de így talán könnyebben követhető a kód. Van aki ezt, van aki azt szereti használni, a végeredményt tekintve teljesen jó mindkét megoldás.

2*. Dátum formázása

Ha eddig mindent jól csináltál, akkor azt tapasztalhatod, hogy még a legrövidebb üzenet is általában két sort foglal el, mert egy igencsak hosszú, csúnya szöveg van kiírva a dátum helyére. Nézzük meg hogy lehet ezen csinosítani!

Jelenleg vagy az Uzenet osztály toString metódusában, vagy (ha megcsináltad a linkelt szorgalmit) egy saját adapterben mondod meg, hogy hogyan nézzen ki egy üzenet a képernyőn. A továbbiak szempontjából ez lényegtelen, csak arra figyelj, hogy a megfelelő osztályban dolgozz. Ha csináltál szorgalmit, akkor a saját adapterben, ha nem, akkor az Uzenet osztályban.

A Date típus egyedül nem tudja magát szépen megformázni, segíteni kell neki. Erre van a SimpleDateFormat típus. Az osztályod elején (de a kapcsos zárójelei között!) hozz létre egy static SimpleDateFormat változót modjuk format névvel. A static kulcsszó azt biztosítja, hogy az összes üzenet egyetlen format objektumra hivatkozzon; elleknező esetben az Azure felhő megzavarodhat, amikor megpróbálod elküldeni az üzenetedet.

A konstruktorában add meg, hogy milyen formátumban írja ki a dátumot: "yyyy-MM-dd HH:mm". Azaz:

format = new SimpleDateFormat("yyyy-MM-dd HH:mm");

Ezután ahol eddig __createdAt.toString()-et használtál írd át erre:

format.format(__createdAt)

Vagy ha az adapterben vagy, akkor ezt: uzenet.__createdAt.toString() erre:

format.format(uzenet.__createdAt)

3*. Ismerkedés a különböző layout-gyökérelemekkel

RelativeLayout?

Android platformon a layout komponensek elrendezésnek több módja is létezik. Mi eddig mindenhol a RelativeLayout-ot használtuk, mint gyökér komponens, ez határozta meg, hogy hogyan helyezhetjük el az egyéb komponenseinket benne.

A RelativeLayout miatt tudtunk olyan tulajdonságokat megadni, hogy pl.: ez a TextView igazodjon a tetejével a másik Button aljához, stb. Aki 9. leckében megpróbálkozott saját maga módosítani a layoutot (hozzáadni a új szó beviteléhez szükséges komponenseket), az tapasztalhatta, hogy a grafikus layout tervező és a RelativeLayout nem mindig csinálja azt, amit szeretnénk.

Ez azért van így, mert a RelatveLayout egy nagyon rugalmas gyökérelem, nagyon sok mindent tud, ám ezt elég nehéz grafikusan szerkeszteni. Egyszerűen túl okos még a legjobb grafikus szerkesztőnek is.

Felmerülhet a kérdés, hogy ha ez ennyire macerás, akkor a komolyabb alkalmazásokat hogy csinálják? Erre két módszer kínálkozik, mi a (talán) egyszerűbbet fogjuk most kipróbálni: az összetett layout-ot.

LinearLayout!

Ehhez ismerkedjünk meg egy másik gyökérelemként használható komponenssel, a LinearLayouttal. Ez egy jóval egyszerűbb komponens, ugyanis a RelativeLayout összevissza, rugalmas pakolhatóságával szemben a LinearLayoutba csak egy sorba (vagy oszlopba), egymás mellé (vagy alá) lehet berakni a komponenseket. Ez a két mód szigorúan kizáró vagy kapcsolatban van egymással, egyszerre egymás mellé és egymás alá nem lehet pakolni. Az elején meg kell adni az orientation tulajdonságában, hogy melyik verziót szeretnénk: a horizontálisat (egymás mellé) vagy a vertikálisat (egymás alá).

Layoutban layout?

Jogos a kérdés, hogy ha egyszerre csak az egyik irányban rakhatjuk egymás után a komponenseket, hogy tudjuk előállítani a leckében használt kinézetet? Szerencsére nagyon egyszerűen, ugyanis a LinearLayout is csak egy komponens; pont, mint egy gomb, vagy TextView. azzal a különbséggel, hogy tartalmazhat más komponenseket is. Vagyis semmi nem akadályoz meg minket abban, hogy mondjuk egy függőleges LinearLayoutba (zölddel) belerakjuk a ListView-t felülre, alá pedig egy másik LinearLayoutot (sárgával), ezúttal vízszinteset, bele pedig egy EditTextet és mellé egy gombot.

Méretezés

Már csak egy dolog maradt hátra: mi mekkora legyen. Ránézésre azt mondanánk, hogy a lista magassága legyen pontosan a (telefon magassága - az alsó rész magassága). Tudjuk pontosan mekkora a telefonunk? Csak-csak. És az alsó rész? Leszámolhatjuk pixelenként, de sosem lesz igazán pontos. És mi van, ha ezek változnak?

Erre való a súlyozás: a LinearLayout lehetőséget ad arra, hogy az egyes komponensek ne mondják meg konkrétan az egyik méretüket (vízszintesnél a szélességet, függőlegesnél a magasságot), hanem csak annyit, hogy a többi komponenshez képest arányosan mekkorák szeretnének lenni. Ehhez a komponensek layout:weight tulajdonságát kell állítani, az értéke pedig közös nevezőre hozás után a számláló értéke legyen. Pl. így:

Ehhez hasonló layoutra viszonylag ritkán van szükség, viszont a súlyozás jó még valamire: Ha van egy (vagy több) fix méretű komponensünk, és szeretnénk egy olyat, ami kitölti a maradék tere, legyen az bármekkora, akkor beállítjuk a layout:weight tulajdonságát 1-re, és már kész is vagyunk. Érdemes ezt tenni a ListView-ek és a belső LinearLayoutban az EditText-tel. Így bármekkora is legyen az alsó komponens, a lista kitölti az összes maradék helyet, és a Küldés gombunkra is írhatunk hosszabb-rövidebb szöveget, az EditText kitölti a maradékot. Pl.:

Érdemes eljátszogatni a különböző kombinációkkal!