21. April 2013

Android: Automatische Drehung einer Activity verhindern

Um in einer Android-App den Orientierungswechsel einer Activity zu unterbinden, wenn das Gerät gedreht wird, gibt es die Möglichkeit, eine bestimmte Orientierung fest vorzuschreiben. Dies kann entweder über ein entsprechendes Attribute in der AndroidManifest.xml oder im Code der Activity geschehen.

Um die Orientierung in der AndroidManifest.xml fest vorzuschreiben, genügt es, die Definition der jeweiligen Activity folgendermaßen zu erweitern:
 <activity  
   android:name=".Activity"  
   android:label="@string/app_name"  
   android:screenOrientation="portrait" >  
   <intent-filter>  
     <action android:name="android.intent.action.MAIN" />  
     <category android:name="android.intent.category.LAUNCHER" />  
   </intent-filter>  
 </activity>  

Durch setzen des Attributs android:screenOrientation auf den Wert "portrait" wird die Orientierung der Activity auf den Portraitmodus, also das Hochkantformat, festgelegt. Soll stattdessen der Landscape-Modus verwendet werden, muss der Wert auf "landscape" gesetzt werden.

Um die Orientierung stattdessen im Code zu setzen muss in der jeweiligen Activity folgender Befehl aufgerufen werden:
 setRequestedOrientation(Configuration.ORIENTATION_PORTRAIT);  

Auch bei dieser Variante muss der Wert ORIENTATION_PORTRAIT auf ORIENTATION_LANDSCAPE geändert werden, falls der Landscape-Modus gesetzt werden soll. Um die Festlegung der Orientierung später wieder aufzuheben, kann der Wert auf ORIENTATION_UNDEFINED gesetzt werden.

Statt die Orientierung bereits im Vorraus fest zu setzen, ist es manchmal gewollt, die Activity der aktuellen Ausrichtung des Geräts nach zu starten, anschließend aber keinen Orientierungswechsel mehr zu erlauben. Ein solches Verhalten ist über folgenden Code realisierbar:
 @Override  
 public final void onCreate(final Bundle savedInstanceState) {  
   super.onCreate(savedInstanceState);  
   setContentView(R.layout.activity_layout);  
   if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {  
     setRequestedOrientation(Configuration.ORIENTATION_PORTRAIT);  
   } else {  
     setRequestedOrientation(Configuration.ORIENTATION_LANDSCAPE);  
   }  
 }  

Hierbei wird nach dem Starten der Activity die aktuelle Orientierung über den Befehl getResources().getConfiguration().orientation ausgelesen und abhängig von ihrem Wert anschließend über den bereits oben erwähnten Befehl festgelegt.

Android: ActionBar verbergen

Um in einer Android-App die ActionBar einer bestimmten Activity zu verbergen, bzw. die Activity als Fullscreen darzustellen, genügt das Hinzufügen eines entsprechenden Attributs in der AndroidManifest.xml. Die Definition der jeweiligen Activity muss dabei, wie in folgendem Beispiel verdeutlicht, um das Attribut android:theme erweitert werden:
 <activity  
   android:name=".Activity"  
   android:label="@string/app_name"  
   android:theme="@android:style/Theme.Light.NoTitleBar.Fullscreen" >  
   <intent-filter>  
     <action android:name="android.intent.action.MAIN" />  
     <category android:name="android.intent.category.LAUNCHER" />  
   </intent-filter>  
 </activity>  
In diesem Beispiel wird die Activity mit einem hellen Hintergrund dargestellt, so wie es bei dem Theme "Holo Light" üblich ist. Wenn die App stattdessen ein dunkles Theme verwendet, muss statt Theme.Light.NoTitleBar.Fullscreen der Wert Theme.Black.NoTitleBar.Fullscreen verwendet werden um einen dunklen Hintergrund darzustellen.

Bei dieser Vorgehensweise ist zu beachten, dass die Activity-Methode getActionBar() eine Null-Referenz zurück liefert, da keine ActionBar erzeugt wird.

19. April 2013

Android: Erkennen wenn App zum ersten Mal gestartet wird

Manchmal kann es bei der Entwicklung einer Android-App hilfreich sein, erkennen zu können wenn diese nach ihrer Installation zum ersten Mal gestartet wird. Diese Erkennung kann man dann beispielsweise nutzen um die Datenbank mit standardmäßigen Einträgen zu füllen oder dem User hilfreiche Tipps einzublenden. Im Folgenden soll kurz aufgezeigt werden, wie so etwas erreicht werden kann.

Die Erkennung sollte in der Activity stattfinden, die beim Starten der App als Erste angezeigt wird. Für die Umsetzung genügt folgender Code:
 public class StartActivity extends Activity {  
   
   SharedPreferences sharedPreferences;  
   
   @Override  
   public void onCreate(Bundle savedInstanceState) {  
     super.onCreate(savedInstanceState);  
     sharedPreferences = getSharedPreferences("preferenceName", MODE_PRIVATE);  
   }  
   
   @Override  
   public void onResume() {  
     super.onResume();  
   
     if (sharedPreferences.getBoolean("firstrun", true)) {  
       sharedPreferences.edit().putBoolean("firstrun", false).commit();  
       // Hier Code einfügen der beim ersten Start einmalig ausgeführt werden soll  
     }  
   }  
   
 }  
Der Code basiert auf der Verwendung von SharedPreferences, die es erlauben, primitive Datentypen zu speichern und zu erhalten, selbst wenn die App beendet wird. In der onCreate-Methode wird zunächst eine solche SharedPreferences-Instanz über einen Namen instanziiert. In der onResume-Methode, die dem Activity-Lifecycle nach erst nach der onCreate-Methode aufgerufen wird, wird versucht einen boolschen Wert mit dem Key "firstrun" aus den SharedPreferences zu laden. Für den Fall, dass dies nicht gelingt, also genau dann, wenn die App zum ersten Mal gestartet wird, wird der Default-Wert "true" angegeben. In diesem Fall wird der Code innerhalb der if-Bedingung ausgeführt, wo nun der Boolean-Wert mit dem Key "firstrun" auf "false" gesetzt werden muss, so dass bei den folgenden Durchläufen die if-Bedingung nie wieder erfüllt wird. Außerdem kann an dieser Stelle derjenige Code eingefügt werden, der beim ersten Start der App ausgeführt werden soll.

9. April 2013

LaTeX: Querverweise auf Kapitel

In LaTeX besteht die Möglichkeit Querverweise auf Kapitel oder Sektionen einzufügen, die den Vorteil haben, dass die Nummer und der Name des Kapitels dynamisch beim Kompilieren des Dokuments eingefügt werden und dadurch bei Änderungen keine manuellen Anpassungen nötig sind. Außerdem kann in einem erzeugten pdf-Dokument direkt per Mausklick zu dem Kapitel gesprungen, auf das verwiesen wurde. Im Folgenden soll nun beispielhaft gezeigt werden, wie ein solcher Querverweis erstellt werden kann.

Zunächst muss natürlich das Kapitel, auf das verwiesen werden soll, definiert werden. Dies geschieht etwa über folgende Zeilen:
 \chapter{Einführung}  
 \label{chapter_einfuehrung}  
Den eigentlichen Querverweis kann man nun über folgende Syntax in das Dokument einfügen. Dabei sind Verweise auf die Kapitelnummer, den Kapitelnamen und auf die Seite, auf der das Kapitel beginnt, möglich.
 Dies ist ein Querverweis auf das Kapitel mit der Nummer \ref{chapter_einfuehrung} und dem Namen \nameref{chapter_einfuehrung}. Es befindet sich auf Seite \pageref{chapter_einfuehrung}.  

Der Bezeichner \ref bezieht sich dabei auf die Kapitelnummer, der Bezeichner \nameref auf den Kapitelnamen und \pageref auf die entsprechende Seite.

Damit das Dokument erfolgreich kompiliert werden kann, muss außerdem in dessen Header über folgende Zeile das Paket "hyperref" eingebunden werden.
 \usepackage{hyperref}  

7. April 2013

Android: Komplexe Objekte in Bundle speichern

In Android ist es nötig, gewisse Zustände einer Activity oder eines Fragments zu speichern und wiederherzustellen, insofern die Werte nicht verloren gehen sollen, wenn die Activity bzw. das Fragment zerstört und neu gestartet wird, wie es z.B. bei einem Orientierungswechsel des Geräts geschieht. Die prinzipielle Vorgehensweise zum Sichern solcher Werte wurde bereits in dem Post "Die Activity-Methoden onSaveInstanceState und onRestoreInstanceState" für Activities und in dem Post "Die Fragment-Methoden onSaveInstanceState und onActivityCreated" für Fragments erklärt.

Meistens handelt es sich bei den Werten, die dabei in dem Bundle-Objekt gespeichert werden um primitive Datentypen. Hierfür bietet die Bundle-Klasse jeweils Methoden um Werte hinzuzufügen, bzw. später wieder auszulesen. Es besteht jedoch darüber hinaus auch die Möglichkeit, komplexe Objekte zu sichern, indem die Methode putSerializable(String key, Serializable value):void zum Ablegen eines Objekts in dem entsprechenden Bundle, bzw. die Methode getSerializable(String key):Serializable zum referenzieren eines zuvor gespeicherten Objekts aus dem Bundle, genutzt wird. Das zu sichernde Objekt muss demnach die das Interface java.io.Serializable implementieren.

Sollen allerdings Objektinstanzen von Klassen gespeichert werden, die dieses Interface nicht implementieren und die nicht angepasst werden können, weil sie beispielsweise Klassen der Java-API oder eines Frameworks sind, steht man vor dem Problem, dass solche Klassen nicht ohne Weiteres an ein Bundle übergeben werden können. Eine Möglichkeit, wie dieses Problem umgangen werden kann, soll nun im Folgenden aufgezeigt werden.

Die Lösung besteht darin, eine Wrapper-Klasse zu schreiben, die das Serializable-Interface implementiert und eine Instanz des zu sichernden Objekttyps aufnimmt. Dabei sind grundsätzlich zwei Ansätze denkbar: Entweder man entscheidet sich für einen sehr einfachen Wrapper, der lediglich eine Getter- und eine Setter-Methode auf das gekapselte Objekt anbietet, oder man setzt auf eine komplexere, aber eleganter zu verwendende Implementierung, die zudem als Proxy fungiert, indem sie neben dem Serializable-Interface auch die Schnittstelle des zu sichernden Objekttyps implementiert und alle Methodenaufrufe an das gekapselte Objekt weiterleitet. Die hat den Vorteil, dass sich Instanzen dieser Klasse exakt wie das Original handhaben lassen. Eventuell bereits vorhandener Code muss auf diese Weise kaum angepasst werden. Auf eine mögliche Implementierung dieser Variante soll nun näher eingegangen werden. Als Code-Beispiel ist im Folgenden ein Wrapper für das Interface java.util.List, das als Schnittstelle für alle Listenimplementierungen der Java-Standardbibliothek dient, zu sehen. Als Szenario für die Sicherung einer solchen Datenstruktur wäre beispielsweise ein Listenadapter denkbar, der Suchergebnisse in einer ListView darstellt. Wenn bei einem Orientierungswechsel der entsprechenden Activity der zeitaufwendige Netzwerkzugriff der Suchanfrage nicht erneut durchgeführt werden soll, muss die zugrunde liegende Datenstruktur zwischengespeichert werden.
 public class SerializableList<Type> implements List<Type>, Serializable {  
   
     private static final long serialVersionUID = 1L;  
   
     private List<Type> list;  
   
     public SerializableList(final List<Type> list) {  
         this.list = list;  
     }  
   
     public final boolean add(final Type object) {  
         return list.add(object);  
     }  
   
     public final void add(final int location, final Type object) {  
         list.add(location, object);  
     }  
   
     public final boolean addAll(final Collection<? extends Type> collection) {  
         return list.addAll(collection);  
     }  
   
     public final boolean addAll(final int index,  
             final Collection<? extends Type> collection) {  
         return list.addAll(index, collection);  
     }  
   
     public final void clear() {  
         list.clear();  
     }  
   
     public final boolean contains(final Object object) {  
         return list.contains(object);  
     }  
   
     public final boolean containsAll(final Collection<?> collection) {  
         return list.containsAll(collection);  
     }  
   
     public final Type get(final int location) {  
         return list.get(location);  
     }  
   
     public final int indexOf(final Object object) {  
         return list.indexOf(object);  
     }  
   
     public final boolean isEmpty() {  
         return list.isEmpty();  
     }  
   
     public final Iterator<Type> iterator() {  
         return list.iterator();  
     }  
   
     public final int lastIndexOf(final Object object) {  
         return list.lastIndexOf(object);  
     }  
   
     public final ListIterator<Type> listIterator() {  
         return list.listIterator();  
     }  
   
     public final ListIterator<Type> listIterator(final int location) {  
         return list.listIterator(location);  
     }  
   
     public final Type remove(final int location) {  
         return list.remove(location);  
     }  
   
     public final boolean remove(final Object object) {  
         return list.remove(object);  
     }  
   
     public final boolean removeAll(final Collection<?> collection) {  
         return list.removeAll(collection);  
     }  
   
     public final boolean retainAll(final Collection<?> collection) {  
         return list.retainAll(collection);  
     }  
   
     public final Type set(final int location, final Type object) {  
         return list.set(location, object);  
     }  
   
     public final int size() {  
         return list.size();  
     }  
   
     public final List<Type> subList(final int start, final int end) {  
         return list.subList(start, end);  
     }  
   
     public final Object[] toArray() {  
         return list.toArray();  
     }  
   
     public final <T> T[] toArray(final T[] array) {  
         return list.toArray(array);  
     }  
   
     @Override  
     public final int hashCode() {  
         final int prime = 31;  
         int result = 1;  
         result = prime * result + ((list == null) ? 0 : list.hashCode());  
         return result;  
     }  
   
     @Override  
     public final boolean equals(final Object obj) {  
         if (this == obj)  
             return true;  
         if (obj == null)  
             return false;  
         if (getClass() != obj.getClass())  
             return false;  
         SerializableList<?> other = (SerializableList<?>) obj;  
         if (list == null) {  
             if (other.list != null)  
                 return false;  
         } else if (!list.equals(other.list))  
             return false;  
         return true;  
     }  
   
 }  
Die Klasse implementiert sowohl das Interface java.io.Serializable, als auch das Interface java.util.List. Außerdem handelt es sich um eine generische Klasse, die mit dem Typ der Objekte, die die gekapselte Liste aufnehmen soll, typisiert wird. Über den Konstruktor wird diese Liste übergeben und in dem entsprechenden Attribut gespeichert. Dieses wird dazu verwendet, um alle Aufrufe der Methoden, die in dem List-Interface definiert sind, unverändert an die gekapselte Liste weiter zu leiten. Darüber hinaus wurde auch jeweils eine Implementierung der hashCode- bzw. equals-Methode hinzugefügt, damit sich Klasseninstanzen tatsächlich so verhalten, wie man es von einer Liste erwarten würde.

Das Abspeichern der oben gezeigten Listenimplementierung in einem Bundle könnte beispielsweise wie folgt aussehen:
 public final void onSaveInstanceState(final Bundle outState) {  
   SerializableList<String> serializableList = new SerializableList<String>(new ArrayList<String>());  
   serializableList.add("elem1");  
   serializableList.add("elem2");  
   outState.putSerializable("list", serializableList);  
 }  
Um die, auf diese Weise im Bundle gespeicherte, Liste anschließend wiederherzustellen, sind dann folgende Zeilen nötig:
 public final void onRestoreInstanceState(final Bundle savedInstanceState) {  
   SerializableList<String> serializableList = (SerializableList<String>) savedInstanceState.getSerializable("list");  
 }  
Dabei ist ein Cast vom Interface Serializable auf die Serializable-List implementierung nötig.