Smarthome App #12: Ereignisse im Smarthome anzeigen


19.05.2017  |  Tutorial, Smarthome App

Im heutigen Beitrag wird die Smarthome App um die Abfrage von Ereignissen erweitert. Seit dem Tutorial "Ereignisse in Datenbank speichern & abfragen", kann der Smarthome-Server Ereignisse in der Datenbank speichern. Mit der Ap war es jedoch bis jetzt nicht möglich, diese Ereignisse abzufragen.

EventFragment implementieren

Als erstes musst du ein neues Fragment erstellen. Dazu klickst du mit rechts auf eine Java-Datei und wählst "New -> Fragment -> Fragment (Blank)".
Zuerst wird das EventFragment erstellt.
Gib dem Fragment den Namen "EventFragment", entferne die Haken bei "Include fragment factory methods?" und "Include interface callbacks?" und klickst auf "Finish". Wechsle zur Layout-Datei des EventFragments namens "fragment_event.xml" und füge dort dem FrameLayout die folgenden Attribute hinzu:
android:background="@color/layoutBackground"
android:id="@+id/frame"
Den TextView-Tag ersetzt du mit einer RecyclerView, der Lade-Animation und einem EmptyItem:
<android.support.v7.widget.RecyclerView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/event_list"></android.support.v7.widget.RecyclerView>

<include
    layout="@layout/loading_animation"
    android:visibility="gone"
    android:id="@+id/loading_animation"/>

<include
    layout="@layout/empty_item"
    android:visibility="gone"
    android:id="@+id/empty_item"/>
Jetzt wird das EventFragment implementiert. Dazu wechselst du zur Datei "EventFragment.java". Dort erstellst du über dem Konstruktor als erstes einige Attribute für das Fragment:
private View eventView;

//RecyclerView
private RecyclerView.Adapter eventAdapter;
private GridLayoutManager glm;
private ArrayList<EventItem> eventItems;
private RecyclerView eventArray;
private View loadingAnimation;

private int visibleItemCount, totalItemCount, pastVisibleItems;
private boolean loading = false;
private int offset = 0;
private boolean endOfItems = false;
String type = "";
Ersetze den Code in der Methode onCreateView() mit dem folgenden Codeblock:
eventView = inflater.inflate(R.layout.fragment_event, container, false);
loadingAnimation = eventView.findViewById(R.id.loading_animation);

//Argumente abfragen
Bundle bundle = getArguments();
if(bundle != null && bundle.containsKey(MainActivity.EXTRA_TYPE)){
    type = bundle.getString(MainActivity.EXTRA_TYPE);
}

eventArray = (RecyclerView) eventView.findViewById(R.id.event_list);
eventArray.setHasFixedSize(true);
glm = new GridLayoutManager(getContext(), getResources().getInteger(R.integer.event_column_count));
glm.setOrientation(GridLayoutManager.VERTICAL);
eventArray.setLayoutManager(glm);
eventArray.addOnScrollListener(new RecyclerView.OnScrollListener() {
    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);
        if(dy > 0){
            visibleItemCount = glm.getChildCount();
            totalItemCount = glm.getItemCount();
            pastVisibleItems = glm.findFirstVisibleItemPosition();
            if(!loading){
                if((visibleItemCount + pastVisibleItems) >= totalItemCount && !endOfItems){
                    loadEvents(offset, MainActivity.LOAD_ON_SCROLL, type);
                }
            }
        }
    }
});

eventItems = new ArrayList<>();

loadEvents(offset, MainActivity.LOAD_ON_START, type);

return eventView;
Dieser Code prüft, ob dem EventFragment ein bestimmter Ereignistyp übergeben wurde und initialisiert danach das Layout. Danach wird der GridLayoutManager mit dem Kontext und der Integer-Ressource "R.integer.event_column_count" initialisiert. Klicke auf die ID und drücke ALT + Eingabe. Wähle hier "Create integer value resource", gebe als Wert 1 ein und klicke auf "OK". Anschließend wird der RecyclerView der LayoutManager hinzugefügt und ein OnScrollListener implementiert, der die Methode loadEvents() aufruft, wenn die Liste bis ganz nach untern gescrollt wurde, damit weitere Ereignisse geladen werden. Am Ende der Methode wird die ArrayList initialisiert, die Methode loadEvents() aufgerufen, um die ersten Ereignisse abzufragen und das Attribut "eventView" zurückgegeben. Als nächstes wird die Klasse EventItem im EventFragment erstellt. FÜge dazu am Ende des Fragments den folgenden Codeblock ein:
private class EventItem{

    private String text, type;

    private long timestamp;
    public EventItem(String text, String type, long timestamp){
        this.text = text;
        this.type = type;
        this.timestamp = timestamp;
    }

    /**
     * Gibt den Text des Items zur&uuml;ck
     * @return
     */
    public String getText(){
        return  text;
    }

    /**
     * Gibt den Typen des Items zur&uuml;ck
     * @return
     */
    public String getType(){
        return type;
    }

    /**
     * Gibt den Zeitstempel des Items zur&uuml;ck
     * @return
     */
    public long getTimestamp(){
        return timestamp;
    }

    /**
     * Gibt den Zeitstempel als formattierten String zur&uuml;ck
     * @return
     */
    public String getFormattedTime(){
        return new SimpleDateFormat("dd.MM.yyyy HH:mm").format(new java.util.Date(timestamp*1000));
    }
}
Die Klasse hat die Attribute "text" und "type", die beide vom Typ "String" sind und ein Attribut namens "timestamp" vom Typ "long". Der Konstruktor erhält einen Wert für jedes Attribut und initialisiert sie damit. Neben den getter-Methoden für alle Attribute besitzt die Klasse außerdem eine Methode namens getFormattedTime(), die den Zeitstempel als String im Format "dd.mm.yyyy hh:mm" ausgibt. Nun wird die Methode loadEvents() erstellt, die als Parameter die zwei Integer "os" und "s" und den String "t" erhält und die entsprechenden Ereignisse vom Server lädt:
/**
 * Fragt die Ereignisse vom Server ab
 * @param os Verschiebung der Ergebnisse
 * @param s Max-Anzahl der Ergebnisse
 * @param t der Typ der abzufragenden Ereignisse
 */
public void loadEvents(int os, int s, String t){
    loading = true;

    if(os == 0){
        loadingAnimation.setVisibility(View.VISIBLE);
    }
    else{
        eventItems.add(null);
        eventAdapter.notifyItemInserted(eventItems.size()-1);
    }

    final Map<String, String> requestData = new HashMap<>();
    requestData.put("action", "getevents");
    requestData.put("username", SaveData.getUsername(getContext()));
    requestData.put("password", SaveData.getPassword(getContext()));
    requestData.put("type", t);
    requestData.put("limit", String.valueOf(s));
    requestData.put("offset", String.valueOf(os));

    HTTPRequest.sendRequest(getContext(), requestData, SaveData.getServerIp(getContext()), new HTTPRequest.HTTPRequestCallback() {
        @Override
        public void onRequestResult(String result) {
            loadingAnimation.setVisibility(View.GONE);

            loading = false;

            boolean firstLoad = false;

            if(offset == 0){
                firstLoad = true;
            }
            else{
                eventItems.remove(eventItems.size()-1);
                eventAdapter.notifyItemRemoved(eventItems.size()-1);
            }

            if(result != null){
                try{
                    JSONObject jsonObject = new JSONObject(result);
                    JSONArray events = jsonObject.getJSONArray("events");

                    for(int i = 0; i < events.length(); i++){
                        offset++;
                        JSONObject o = events.getJSONObject(i);
                        eventItems.add(new EventItem(o.getString("text"), o.getString("type"), o.getLong("time")));
                    }
                }
                catch (Exception e){
                    Dialogs.fehlermeldung("Fehler beim Laden der Ereignisse", eventView.findViewById(R.id.frame));
                }
            }
            else{
                Dialogs.fehlermeldung("Es konnten keine Ereignisse geladen werden", eventView.findViewById(R.id.frame));
            }

            if(firstLoad){
                if(eventItems.size() > 0){
                    eventAdapter = new EventAdapter();
                    eventArray.setAdapter(eventAdapter);
                }
                else{
                    eventArray.setVisibility(View.GONE);
                    eventView.findViewById(R.id.empty_item).setVisibility(View.VISIBLE);
                    ((ImageView) eventView.findViewById(R.id.empty_icon)).setImageResource(Icons.getDrawerIcon("events"));
                    ((TextView) eventView.findViewById(R.id.empty_title)).setText("Keine Ereignisse");
                    ((TextView) eventView.findViewById(R.id.empty_info)).setText("Es liegen keine Ereignisse vor.");
                }
            }
            else{
                if(result.equals("{\"events\":[]}")) endOfItems = true;
                loading = false;
                eventAdapter.notifyDataSetChanged();
            }
        }

        @Override
        public void onError(String msg) {
            loading = false;

            loadingAnimation.setVisibility(View.GONE);

            eventView.findViewById(R.id.empty_item).setVisibility(View.VISIBLE);
            ((ImageView) eventView.findViewById(R.id.empty_icon)).setImageResource(Icons.getDrawerIcon("events"));
            ((TextView) eventView.findViewById(R.id.empty_title)).setText("Keine Ereignisse");
            ((TextView) eventView.findViewById(R.id.empty_info)).setText("Es konnten keine Ereignisse geladen werden.");
        }
    });
}
Die Methode startet eine HTTP-Anfrage an den Server, die bei Erfolg ein neues JSON-Array erstellt. Dieses Array wird durchlaufen und der Liste "eventList" werden die entsprechenden Ereignisse hinzugefügt. War es der erste Aufruf von loadEvents(), wird ein neuer EventAdapter erstellt und der RecyclerView zugewiesen. Andernfalls wird der EventAdapter mit dem Aufruf "eventAdapter.notifyDataSetChanged()" über die Änderungen benachrichtigt. Im Fehlerfall wird das EmptyItem angezeigt, mit der Meldung, dass die Ereignisse nicht geladen werden konnten. Jetzt wird der EventAdapter erstellt, der die Listenelemente anzeigt und verwaltet. Füge dazu den folgenden Code-Block in das Fragment ein.
public class EventAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>{

    final int VIEW_TYPE_EVENT = 0;
    final int VIEW_TYPE_LOADING = 1;
    int lastPosition = -1;

    @Override
    public int getItemCount(){
        return eventItems.size();
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int i){
        if(holder instanceof EventViewHolder){
            final EventItem ei = eventItems.get(i);
            final EventViewHolder eventViewHolder = (EventViewHolder) holder;
            eventViewHolder.event.setText(ei.getText());
            eventViewHolder.type.setText(ei.getType());
            eventViewHolder.time.setText(ei.getFormattedTime());
            setAnimation(((EventViewHolder) holder).container, i);
        }
    }

    /**
     * Startet die Animation auf der View
     * @param viewToAnimate View
     * @param position Index der View
     */
    private void setAnimation(View viewToAnimate, int position){
        if(position > lastPosition){
            lastPosition = position;
            Animation animation = AnimationUtils.loadAnimation(getContext(), R.anim.recycler_animation);
            viewToAnimate.startAnimation(animation);
        }
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType){
        View itemView;
        switch (viewType){
            case VIEW_TYPE_EVENT:
                itemView = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.event_item, viewGroup, false);
                return new EventViewHolder(itemView);
            case VIEW_TYPE_LOADING:
                itemView = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.loading_row, viewGroup, false);
                return new LoadingViewHolder(itemView);
        }
        return null;
    }

    @Override
    public int getItemViewType(int position){
        return eventItems.get(position) == null ? VIEW_TYPE_LOADING : VIEW_TYPE_EVENT;
    }

    public class EventViewHolder extends RecyclerView.ViewHolder{

        protected TextView event, time, type;
        protected View container;

        public EventViewHolder(View v){
            super(v);
            container = v.findViewById(R.id.container);
            event = (TextView) v.findViewById(R.id.event);
            time = (TextView) v.findViewById(R.id.time);
            type = (TextView) v.findViewById(R.id.type);
        }
    }

    public class LoadingViewHolder extends RecyclerView.ViewHolder{
        public LoadingViewHolder(View v){
            super(v);
        }
    }
}

Layout-Dateien für Listenelemente erstellen

Als nächstes werden die Layouts "event_item.xml" und "loading_row.xml" erstellt. Dazu klickst du mit rechts auf eine Layout-Datei und wählst "New -> Layout resource file".
So wird eine Layout-Datei erstellt.
Gib dem Layout den namen "event_item" und klicke auf "OK". Ändere im Layout als erstes den LinearLayout-Tag in ein RelativeLaoyout-Tag und entferne das Attribut "orientation". Füge stattdessen das Attribut "id" mit dem Wert "@+id/container" hinzu Außerdem änderst du den Wert des Attributs "layout_height" von "match_parent" auf "wrap_content". In den RelativeLayout-Tag fügst du eine CardView mit LinearLayouts und TextViews ein, die die Informationen anzeigen:
<android.support.v7.widget.CardView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:cardElevation="@dimen/cardViewElevation"
    app:cardCornerRadius="@dimen/cardViewCornerRadius"
    app:cardUseCompatPadding="true">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:layout_margin="10dp"
                android:textAppearance="@style/Base.TextAppearance.AppCompat.Medium"
                android:id="@+id/type"
                android:textStyle="bold"/>

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:layout_margin="10dp"
                android:textAppearance="@style/Base.TextAppearance.AppCompat.Medium"
                android:id="@+id/time"
                android:textStyle="bold"/>

        </LinearLayout>

       <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:layout_margin="10dp"
            android:textAppearance="@style/Base.TextAppearance.AppCompat.Medium"
            android:id="@+id/event"
            android:textStyle="bold"/>

    </LinearLayout>

</android.support.v7.widget.CardView>
Erstelle nun wie gerade eben eine neue Layout-Datei und gib ihr den Namen "loading_row". Füge hier dem LinearLayout-Tag das Attribut "padding" mit dem Wert "7dp" hinzu und ändere den Wert von "layout_height" von "match_parent" in "wrap_content". Danach fügst du in das LinearLayout ein RelativeLayout mit einer ProgressBar ein, damit der Layout-Code so aussieht:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="7dp">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="75dp"
        android:layout_margin="5dp"
        android:layout_gravity="center">

        <ProgressBar
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:indeterminate="true"/>

    </RelativeLayout>

</LinearLayout>
Jetzt, da die benötigten Layouts angelegt sind, können noch die statischen Variablen in der MainActivity erstellt werden. Wechsle dazu zur Datei "MainActivity.java". Hier fügst du eine Variable für Intent-Extras hinzu, die einen Typen identifiziert:
final static String EXTRA_TYPE = "EXTRA_TYPE";
Außerdem werden Variablen hinzugefügt, die angeben, wie viele Listenelemente beim Start und beim Scrollen geladen werden sollen:
//Zu ladende Listenelemente
public final static int LOAD_ON_START = 10;
public final static int LOAD_ON_SCROLL = 5;
Damit auch nur Ereignisse eines bestimmten Typs angezeigt werden können, wird ein weiterer Menüpunkt zur ActionBar der MainActivity hinzugefügt. Dazu öffnest du die Datei "/res/menu/main.xml" und einen weiteren Punkt hinzu:
<item
    android:id="@+id/action_event_type"
    android:orderInCategory="100"
    android:title="Ereignis-Typ w&auml;hlen"
    android:visible="false"
    app:showAsAction="never" />

MainActivity anpassen

Anschließend wird in der MainActivity über der onCreate()-Methode neue eine Variable namens "optionsMenu" vom Typ "Menu" erstellt. In der Methode onCreateOptionsMenu() wird die Variable "optionsMenu" mit dem Wert des Methodenparameters "menu" initialisiert, indem du in die Methode die folgende Zeile einfügst:
optionsMenu = menu;
Der Methode onOptionsItemSelected() wird ein neuer if-Block hinzugefügt, für den Fall, dass die Id des Items den Wert "R.id.action_event_type" hat. In diesem Fall wird die (noch nicht existierende) Methode loadEventTypes()aufgerufen:
else if(post_id == R.id.action_event_type){
    loadEventTypes();
}
Da der Menüpunkt außerdem nur dann sichtbar sein soll, wenn man sich im EventFragment befindet, wird der Methode onNavigationItemSelected() unterhalb der Wertezuweisung der Variable "position" der folgende Codeblock hinzugefügt:
//Optionsitems setzen (wenn optionsMenu != null)
if(optionsMenu != null){
    if(drawerItemList.get(position).getFragment() instanceof EventFragment){
        optionsMenu.findItem(R.id.action_event_type).setVisible(true);
    }
    else{
        optionsMenu.findItem(R.id.action_event_type).setVisible(false);
    }
}
Damit wird der Menüpunkt sichtbar gemacht, wenn das EventFragment angezeigt wird und andernfalls unsichtbar gemacht. Um zum EventFragment zu gelangen, muss erst ein Punkt zum NavigationDrawer hinzugefügt werden. Dazu wird in der Methode createRooms() über dem Code zum hinzufügen des SettingsFragments (bei mir in Zeile 247) der folgende Code hinzugefügt:
drawerItemList.add(new DrawerItem("Ereignisse", Icons.getDrawerIcon("events"), "events", new EventFragment()));

//Zur Abfrage der Ereignisanzahl
int eventsIndex;

if(!drawerItemList.isEmpty()){
    eventsIndex = drawerItemList.size()-1;
    loadEventCount(eventsIndex);
}
else{
    Dialogs.fehlermeldung("Anzahl der ungelesenen Ereignisse kann nicht geladen werden.", findViewById(R.id.frame));
}
Mit diesem Code wird der Menüpunkt für das EventFragment zum NavigationDrawer hinzugefügt und die (ebenfalls noch nicht existierende) Methode loadEventCount() mit der Position des Menüpunktes im Drawer als Parameter aufgerufen. In der MainActivity befindet sich auch noch die Methode fehlermeldung(), die bei einem der letzten Tutorials aus allen Klassen entfernt, in der MainActivity jedoch übersehen wurde. Lösche die Methode daher und ersetze alle vorhanden Aufrufe mit "Dialogs.fehlermeldung()", wobei du als zweiten Parameter "findViewById(R.id.frame)" angibst. Damit sollten die Aufrufe nun in etwa so aussehen:
Dialogs.fehlermeldung("Fehler beim Laden der R&auml;ume", findViewById(R.id.frame));
Als nächstes wird die Methode loadEventCount() implementiert. Füge dazu den folgenden Code in die MainActivity ein:
/**
 * Fragt die Anzahl der ungelesenen Ereignisse vom Server ab und aktualisiert das ensprechende Drawer-Item
 * @param indexOfEventDrawerItem Index des Drawer-Items
 */
public void loadEventCount(final int indexOfEventDrawerItem){

    Map<String, String> requestData = new HashMap<>();
    requestData.put("action", "getunseenevents");
    requestData.put("username", SaveData.getUsername(getApplicationContext()));
    requestData.put("password", SaveData.getPassword(getApplicationContext()));

    HTTPRequest.sendRequest(getApplicationContext(), requestData, SaveData.getServerIp(getApplicationContext()), new HTTPRequest.HTTPRequestCallback() {
        @Override
        public void onRequestResult(String result) {
            switch(result){
                default:
                    try{
                        int eventCount = Integer.parseInt(result);
                        if(eventCount > 0){
                            MenuItem item = drawerMenu.getItem(indexOfEventDrawerItem);
                            String title = "Ereignisse ("+eventCount+")";
                            item.setTitle(title);
                            drawerItemList.get(indexOfEventDrawerItem).name = title;
                        }
                    }
                    catch (Exception e){
                        e.printStackTrace();
                    }
                    break;
            }
        }

        @Override
        public void onError(String msg) {
            Dialogs.fehlermeldung("Anzahl der ungelesenen Ereignisse kann nicht geladen werden.", findViewById(R.id.frame));
        }
    });
}
Diese Methode fragt die Anzahl der Ereignisse ab, die der eingeloggte Nutzer noch nicht gelesen hat. Ist diese Abfrage erfolgreich und die Anzahl größer als 0, so wird der TItel des Menüelements des NavigationDrawers an der angegebenen Position auf den Wert "Ereignisse("+eventCount+")" gesetzt. Außerdem wird der Titel des entsprechenden DrawerItems auf diesen Wert gesetzt. Gibt es bei der Abfrage einen Fehler, so wird "Anzahl der ungelesenen Ereignisse kann nicht geladen werden." ausgegeben. Bevor die Methode loadEventTypes() erstellt wird, wird noch die Methode singleChoiceDialog() in der Klasse Dialogs erstellt, da sie von loadEventTypes() verwendet wird. Öffne dazu die Datei "Dialogs.java" und erstelle dort die Methode singleChoiceDialog():
/**
 * Erstellt einen Dialog, um ein Item auszuw&auml;hlen
 * @param title Titel des Dialogs
 * @param cancelButtonText Titel des "Abbrechen"-Buttons
 * @param context Kontext der App
 * @param items Zur Wahl stehende Items
 * @param onOk Listener f&uuml;r Item-Klicks
 * @param onCancel Listener f&uuml;r Cancel-Klicks
 */
public static void singleChoiceDialog(String title, String cancelButtonText, Context context, 
    String[] items, final DialogInterface.OnClickListener onOk, final DialogInterface.OnClickListener onCancel){
    AlertDialog.Builder builder = new AlertDialog.Builder(context);

    builder.setTitle(title);

    builder.setItems(items, onOk);

    builder.setNegativeButton(cancelButtonText, onCancel);

    builder.show();
    
}
Mit der Methode singleChoiceDialog() wird ein Dialog erstellt, der eine Liste von Elementen anzeigt, von denen eines ausgewählt werden kann. Nun kann die Methode loadEventTypes() in der MainActivity erstellt werden. Füge dazu den folgenden Codeblock in die MainActivity ein:
/**
 * L&auml;dt alle Ereignis-Typen vom Server
 */
public void loadEventTypes(){

    final Map<String, String> requestData = new HashMap<>();
    requestData.put("action", "geteventtypes");
    requestData.put("username", SaveData.getUsername(getApplicationContext()));
    requestData.put("password", SaveData.getPassword(getApplicationContext()));

    HTTPRequest.sendRequest(getApplicationContext(), requestData, SaveData.getServerIp(getApplicationContext()), new HTTPRequest.HTTPRequestCallback() {
        @Override
        public void onRequestResult(String result) {
            switch (result){
                default:
                    try{
                        JSONObject types = new JSONObject(result);

                        JSONArray eventTypes = types.getJSONArray("types");

                        final String[] items = new String[eventTypes.length()];

                        for(int i = 0; i < eventTypes.length(); i++){
                            items[i] = eventTypes.getString(i);
                        }

                        Dialogs.singleChoiceDialog("Ereignistypen w&auml;hlen", "Abbrechen", MainActivity.this, items, new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                Intent intent = new Intent(MainActivity.this, EventActivity.class);
                                intent.putExtra(MainActivity.EXTRA_TYPE, items[which]);
                                startActivity(intent);
                                dialog.dismiss();
                            }
                        }, new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                dialog.dismiss();
                            }
                        });
                    }
                    catch (Exception e){
                        e.printStackTrace();
                        Dialogs.fehlermeldung("Ereignistypen konnten nicht geladen werden", findViewById(R.id.frame));
                    }
                    break;
            }
        }

        @Override
        public void onError(String msg) {
        }
    });
}
Die Methode fragt die vorhandenen Ereignistypen vom Server ab und schreibt sie in ein String-Array. Anschließend wird die Methode singleChoiceDialog() der Klasse Dialogs mit einem Titel, einem Text für den Abbrechen-Button, dem String-Array und zwei OnClickListenern (einer für die Auswahl des Items und einer für den Abbrechen-Button). Nach Auswahl eines Items wird ein neuer Intent erstellt, der mit dem gewählten Item als Extra die Activity EventActivity startet.

EventActivity implementieren

Da die EventActivity noch nicht existiert, wird sie nun erstellt. Klicke dazu mit rechts auf eine Java-Datei und wähle "New -> Activity -> Empty Activity".
Erstelle eine neue leere Activity.
Gib der Activity den Namen "EventActivity" und klicke auf "Finish".
Gib der Activity den Namen
Zuerst wird das Layout der EventActivity angepasst. Dazu öffnest du die Datei "activity_event.xml" und änderst den ConstraintLayout-Tag in einen FrameLayout-Tag. Außerdem fügst du dem Tag das Attribut "id" mit dem Wert "@+id/frame" hinzu. Jetzt sollte der Code derLayout-Datei so aussehen:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/frame"
    tools:context="de.smarthome_blogger.smarthome.EventActivity">

</FrameLayout>
Jetzt wird der Activity ein Menü für die ActionBar hinzugefügt.
Erstelle eine neue Layout-Datei für die EventActivity.
Gib dem Menü den Namen "menu_event" und klicke auf "OK". Füge dem Menü ein neues Element hinzu:
<item
    android:id="@+id/action_event_type"
    android:orderInCategory="100"
    android:title="Ereignis-Typ w&auml;hlen"
    app:showAsAction="always"/>
Wechsle nun zur Datei EventActivity und erstelle über der Methode onCreate() eine private String-Variable namens "type". Danach fügst du in die MainActivity den folgenden Code ein:
getSupportActionBar().setDisplayHomeAsUpEnabled(true);

//Intent abfangen
Intent intent = getIntent();

if(intent != null && intent.hasExtra(MainActivity.EXTRA_TYPE)){
    type = intent.getStringExtra(MainActivity.EXTRA_TYPE);
}

setTitle(type);

//EventFragment einsetzen
Bundle bundle = new Bundle();
bundle.putString(MainActivity.EXTRA_TYPE, type);
Fragment eventFragment = new EventFragment();
eventFragment.setArguments(bundle);

FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.frame, eventFragment);
fragmentTransaction.commit();
Dieser Code fügt der ActionBar einen Pfeil hinzu, damit der Nutzer zurück navigieren kann. Danach wird geprüft, ob die Activity einen Intent erhalten hat, der einen Typen enthält. Wenn ja, wird die Variable "type" mit diesem Typen initialisiert. Als nächstes wird der Titel dieser Activity auf den Wert von "type" gesetzt. Nun wird ein neues EventFragment erstellt, dem ein Bundle-Objekt mit dem Typen übergeben wird. Anschließend wird ein Objekt vom Typ FragmentTransaction initialisiert und das Fragment in die Activity eingesetzt. Unterhalb der onCreate()-Methode fügst du die folgenden drei Methoden ein:
@Override
public boolean onSupportNavigateUp(){
    super.onSupportNavigateUp();
    onBackPressed();
    getSupportFragmentManager().popBackStack();
    return true;
}

@Override
public  boolean onCreateOptionsMenu(Menu menu){
    getMenuInflater().inflate(R.menu.menu_event, menu);
    return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item){
    switch(item.getItemId()){
        case R.id.action_event_type:
            loadEventTypes();
            break;
    }
    return super.onOptionsItemSelected(item);
}
Die Methode onSupportNavigateUp() wird aufgerufen, wenn der Nutzer auf den Pfeil in der ActionBar klickt und schließt die Activity. Um das Optionsmenü zur ActionBar hinzuzufügen, wird die Methode onCreateOptionsMenu() aufgerufen, die das Layout der Menüdatei in die ActionBar einfügt. Die Methode onOptionsItemSelected() wird aufgerufen, wenn ein Menüpunkt angeklickt wird. Mit einem switch-Block wird geprüft, welche ID das angetippte Element hat. Ist diese ID "R.id.action_event_type", so wird die Methode loadEventTypes() aufgerufen. Diese Methode existiert noch nicht. Da sie bereits in der MainActivity implementiert wurde aber leicht angepasst werden muss, wird die Methode einfach aus der MainActivity herauskopiert und in die EventActivity eingefügt:
/**
 * L&auml;dt alle Ereignis-Typen vom Server
 */
public void loadEventTypes(){
    final Map<String, String> requestData = new HashMap<>();
    requestData.put("action", "geteventtypes");
    requestData.put("username", SaveData.getUsername(getApplicationContext()));
    requestData.put("password", SaveData.getPassword(getApplicationContext()));
    HTTPRequest.sendRequest(getApplicationContext(), requestData, SaveData.getServerIp(getApplicationContext()), new HTTPRequest.HTTPRequestCallback() {
        @Override
        public void onRequestResult(String result) {
            switch (result){
                default:
                    try{
                        JSONObject types = new JSONObject(result);

                        JSONArray eventTypes = types.getJSONArray("types");

                        final String[] items = new String[eventTypes.length()];

                        for(int i = 0; i < eventTypes.length(); i++){
                            items[i] = eventTypes.getString(i);
                        }

                        Dialogs.singleChoiceDialog("Ereignistypen w&auml;hlen", "Abbrechen", MainActivity.this, items, new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                Intent intent = new Intent(MainActivity.this, EventActivity.class);
                                intent.putExtra(MainActivity.EXTRA_TYPE, items[which]);
                                startActivity(intent);
                                dialog.dismiss();
                            }
                        }, new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                dialog.dismiss();
                            }
                        });
                    }
                    catch (Exception e){
                        e.printStackTrace();
                        Dialogs.fehlermeldung("Ereignistypen konnten nicht geladen werden", findViewById(R.id.frame));
                    }
                    break;
            }
        }
        @Override
        public void onError(String msg) {
        }
    });
}
Dort, wo die Methode "MainActivity.this" aufruft (bei mir in Zeile 102 & 105), setzt du stattdessen "EventActivity.this" ein. Damit ist die EventActivity fertig implementiert und das Ende dieses Tutorials erreicht. Du kannst jetzt mit deiner Smarthome-App alle Ereignisse vom Server abfragen und dir wird die Anzahl der ungelesenen Ereignisse im NavigationDrawer angezeigt. Bei Fragen oder Problemen kannst du mir gerne einen Kommentar hinterlassen.

Über den Autor


Sascha Huber

Hallo, ich bin Sascha, der Gründer von Smarthome Blogger.

Mit einer Leidenschaft für Technologie und einem Hintergrund als Software Engineer habe ich 2016 Smarthome Blogger gegründet. Mein Ziel war es schon immer, innovative Lösungen zu entdecken, die unser Leben einfacher und intelligenter gestalten können. In meinem beruflichen Leben arbeite ich täglich mit Software und Technik, aber auch in meiner Freizeit bin ich stets auf der Suche nach neuen technischen Spielereien und Möglichkeiten, mein Zuhause zu automatisieren und zu verbessern.

Auf Smarthome Blogger teile ich mein Wissen, meine Erfahrungen und meine Begeisterung für alles rund um das Thema Smarthome.



Dieser Beitrag hat dir gefallen?

Dann abonniere doch unseren Newsletter!