2015-05-30

Seifenkisten wieder im Rennen


In meinem Post Seifenkisten auf Abwegen hatte ich von meinen frustrierenden Erfahrungen mit JAX-WS unter Android berichtet. Im aktuellen KaffeeKlatsch zeige ich, wie man mittels kSOAP auf recht einfache Weise doch noch auf SOAP-Services zugreifen kann.




2015-05-17

Know thy tags...

Mit den beiden Swing-Klassen RTFEditorKit und HTMLEditorKit lässt sich recht schnell ein einfacher HTML-nach-RTF-Converter schreiben. Wie, ist unter anderem in diesem Stackoverflow-Post beschrieben.

Aufpassen muss man allerdings bei einigen Tags. Zumindest <em> und <strong> werden nicht in RTF-Codes umgesetzt, man sollte sie also von Hand in <b> und <i> konvertieren.

2015-05-15

Mac OS X-Quicktipp: eingebauten Webserver nutzen

Mac OS X hat schon seit vielen Versionen einen eigenen Webserver an Board. Früher konnte man ihn über ein eigenes Modul in den Systemeinstellungen konfigurieren. Unglücklicherweise hat Apple es vor einiger Zeit entfernt. Der Webserver an sich ist aber noch vorhanden und funktoniert auch.

Bevor man ihn mit sudo apachectl start startet, legt man den Inhalt unter /Library/WebServer/Documents ab. Für den Schreibzugriff auf dieses Verzeichnis sind Admin-Rechte erforderlich.

Danach kann man im Browser mit http://localhost/... auf die Seiten zugreifen.

Weitere Kommandos für apachectl sind stop und restart.

Zwar bringen viele Tools und Sprachen ihren eigenen Webserver mit, aber ich finde, es ist gut zu wissen, dass man auch ohne Download schnell mal lokale Tests aufsetzen kann.

2015-05-14

Mac-Quicktipp: Schlagschatten in Screenshots loswerden

Kennen Sie das auch? Dinge, die nur ein klein wenig nerven, toleriert man recht lange. Bei mir waren das die Schlagschatten, die Mac OS X Screenshots hinzufügt. Dabei sind nur zwei Eingaben im Terminal nötig, um Bloggern das Leben leichter zu machen:

defaults write com.apple.screencapture disable-shadow true schaltet den Schlagschatten aus.

killall SystemUIServer sorgt dafür, dass die Änderungen übernommen werden.

2015-05-09

Living in the past (Teil 2)

In meinem vorherigen Post habe ich angedeutet, dass man unter neueren Android-Versionen Live Folder noch nutzen kann, obwohl Google die korrespondierenden Klassen mit API-Level 14 für veraltet erklärt und den Zugriff über den Home Screen ausgebaut hat.

Das Installieren einer App, die Live Folder bereitstellt, klappt selbst unter Android 5.1 noch reibungslos. Die beteiligten Frameworksklassen sind also (zum Glück) noch vorhanden. Eigentlich ist das auch logisch, denn das harte Entfernen hielte ich schon für einen ziemlich signifikanten Einschnitt. Über den Home Screen ist das Hinzufügen von Live Foldern aber nicht mehr möglich. Die Frage ist also, wie man Zugriff erhält. Hierzu muss man wissen, dass Live Folder keine Benutzeroberfläche bereitstellen, sondern nur die Daten, und zwar in Gestalt eines (auf eine ganz bestimmte Weise konfektionierten) Content Providers.

Die Idee ist deshalb, selbst für die UI zu sorgen, und den Content Provider einfach nach seinen Daten zu fragen. Die folgende Activity zeigt einen entsprechenden Proof of Concept, den ich Tin Opener genannt habe. Ich werde die App in den nächsten Tagen auf Bitbucket bereitstellen. Wenn das erledigt ist, füge ich diesem Post einfach einen Kommentar hinzu.

public class TinOpenerActivity extends ListActivity 
    implements LoaderManager.LoaderCallbacks<Cursor> {

    private static final String TAG = 
            TinOpenerActivity.class.getSimpleName();
    private static final int RQ_PICK_LIVE_FOLDER = 42;
    private static final int URL_LOADER = 0;

    private Uri uri;
    private String[] mFromColumns = {
            LiveFolders.NAME, LiveFolders.DESCRIPTION
    };
    private int[] mToFields = {
            android.R.id.text1, android.R.id.text2
    };
    private SimpleCursorAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setListAdapter(adapter = new SimpleCursorAdapter(this, 
                android.R.layout.simple_list_item_2, null, 
                mFromColumns, mToFields, 0));
        getListView().setOnItemClickListener(
            new AdapterView.OnItemClickListener() {

            @Override
            public void onItemClick(AdapterView<?> parent, 
                                    View view, int position, 
                                    long id) {
                Cursor c = (Cursor) adapter.getItem(position);
                if (c != null) {
                    String url = c.getString(
                        c.getColumnIndex(LiveFolders.INTENT));
                    if (url != null) {
                        Intent i = new Intent(Intent.ACTION_VIEW);
                        i.setData(Uri.parse(url));
                        startActivity(i);
                    }
                }
            }
        });
        Intent i = new Intent(LiveFolders.ACTION_CREATE_LIVE_FOLDER);
        Intent chooser = Intent.createChooser(i, 
                getString(R.string.choose));
        startActivityForResult(chooser, RQ_PICK_LIVE_FOLDER);
    }

    @Override
    protected void onActivityResult(int requestCode, 
                                    int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if ((RESULT_OK == resultCode) && 
                (RQ_PICK_LIVE_FOLDER == requestCode)) {
            if (data != null) {
                uri = data.getData();
                if (uri != null) {
                    getLoaderManager().initLoader(URL_LOADER, 
                            null, this);
                }
            }
        }
    }

    @Override
    public Loader<Cursor> onCreateLoader(int loaderID, 
                                         Bundle args) {
        switch (loaderID) {
            case URL_LOADER:
                return new CursorLoader(this, uri, 
                        null, null, null, null);
            default:
                return null;
        }
    }

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
        adapter.changeCursor(data);
    }

    @Override
    public void onLoaderReset(Loader<Cursor> loader) {
    }
}

Die App TinOpener ist sehr einfach gestrickt. Sie besteht aus einer ListActivity, die ihre Daten von einem SimpleCursorAdapter bezieht. So wie sich das gehört, geschieht das Laden mit Hilfe eines Loaders im Hintergrund.

Die eigentlich spannende Frage ist, wie Live Folder gefunden werden. Hierfür baue ich ein Intent mit der Action LiveFolders.ACTION_CREATE_LIVE_FOLDER und lasse es auf die systemeigene Intent-Auswahl los. Sind mehrere Live Folder installiert, kann der Benutzer einen auswählen. Ist nur einer vorhanden, wird dieser automatisch angezeigt.

Statt in eine Activity könnte man dies auch in ein Appwidget packen. Na, hat jemand Lust darauf?

Nachtrag: Ja, das Listing enthält keine Kommentare... Ich werde dies in der Version auf Bitbucket nachholen. Und den Fall berücksichtigen, dass gar kein Live Folder vorhanden ist. Dann sollte zumindest ein entsprechender Hinweis erfolgen.

2015-05-07

Living in the past (Teil 1)

In diesem Post schwelgen wir in einer (weit zurück liegenden) Vergangenheit…

Android hat viele Features kommen und gehen sehen. Ein schönes Beispiel hierfür sind die sogenannten Live Folder. Diese wurden mit API-Level 3 (Cupcake) eingeführt und mit API-Level 14 (Ice Cream Sandwich) für veraltet erklärt. Hier ein paar Screenshots unter Android 2.3.7.

Tippen und Halten an einer freien Stelle des Home Screens öffnet das folgende Popup-Menü:

Live Folder #1

Tippt man auf Folders, öffnet sich ein weiteres Menü. Hier ist eine App von mir, die Live Folder zur Verfügung stellt, zu sehen. LiveFolderDemo war Bestandteil meines Buches Android 3.

Live Folder #2

In der Folgeauflage, Android 4, musste ich das Projekt schon wieder entfernen, da Google die API für veraltet erklärt und in der Entwicklerdoku mit deutlichen Worten von der Nutzung abgeraten hat:

Screenshot der Entwicklerdoku

Tippt man auf den Namen eines Live Folders, wird ein Icon auf dem Hintergrund des Home Screens abgelegt.

Live Folder #3

Das Antippen des Icons öffnet den Live Folder.

Live Folder #4

Ich habe auf Bitbucket ein Reopsitory abgelegt, das die App LiveFolderDemo enthält. Denn auch wenn man den Code in modernen Android-Versionen (eigentlich…) nicht mehr nutzen kann, wäre es doch schade, in einfach so zu löschen…

Und wenn Sie wissen wollen, was es mit eigentlich auf sich hat, warten Sie einfach auf einen der folgenden Posts.

2015-05-03

TKWeek 1.8.0

Nach einer etwas längeren Pause gibt es ein Update von TKWeek. Es waren ein paar Anpassungen an Lollipop nötig. Insbesondere die Navigation musste ich anpassen. Jetzt sollte aber wieder alles wie gewohnt funktionieren.

Screenshot TKWeek