Vorgestellt: Tuck
17. März 2019
Vor kurzem habe ich eine kleine Android-App namens Tuck geschrieben. Eine Anwendung, mit der man direkt aus Instagram heraus fremde Beiträge lokal abspeichern kann. Warum und wie ich das gemacht habe möchte ich erläutern.
Warum
…möchte ich fremde Beiträge abspeichern? Zum einen, weil man in Instagram einfach nicht ordentlich vergrößern kann. Möchte ich Details eines Bildes sehen, bleibt mir nur die bekannte Spreizbewegung der Finger, die zwangsläufig das halbe Bild verdeckt. Lässt man los, ist man wieder auf Ausgang. Nicht optimal.
Zum anderen heißt es zwar immer „das Internet vergisst nichts“, löscht aber ein Nutzer seinen Account, sind alle seine Bilder ebenfalls erst mal weg. Ich habe mir eine Sammlung Bucketlist angelegt, mit Bildern von Orten die ich einmal besuchen möchte. Niemand garantiert, dass alle Bilder in der Sammlung dauerhaft verfügbar bleiben. Eine lokale Kopie ist hier die Lösung
YAID – Yet another Instagram Downloader
So schaute ich mich nach einer Lösung um. Es gibt bereits eine unfassbare Zahl an ähnlichen Apps.
Unter den ersten 20 Suchtreffern ist jedoch keine einzige ohne Werbung. Zumeist von der üblen Sorte: Full-Screen, blinkend, Videos mit Mindestbetrachtdauer und das alles im Überfluss. Die User-Interfaces meist nur Web-Apps in einem Container. So unseriös wie sie wirken darf man sich berechtigte Sorgen darüber machen obendrein noch ausspioniert zu werden.
Für die simple Aufgabe die zu leisten ist, finde ich das untragbar. Daher habe ich mich selbst daran gemacht eine schlanke, unaufdringliche, zuverlässige und vor allem sichere Variante zu entwickeln.
Anforderungen
Ich habe mir überlegt, wie ich den fehlenden Download-Button in Instagram am besten nachbauen könnte.
- Es soll mit möglichst wenigen Klicks ans Ziel gekommen werden.
- Man sollte keine zweite App händisch öffnen müssen. Nicht einmalig und schon gar nicht mehrmalig.
- Die App sollte so unaufdringlich wie möglich sein, am Besten im Hintergrund unbemerkt agieren.
Die Umsetzung
Teil A: User Interface/Experience
Irgendwie muss aus Instagram heraus Tuck darüber benachrichtigt werden, dass ein Beitrag heruntergeladen werden soll.
Android unterstützt glücklicherweise die Möglichkeit dass Apps sich untereinander austauschen. Auch Instagram kann einen Link auf einen Beitrag über Teilen via…
bzw. Teile Link…
(je nach Version) als schlichten Text-Intent an andere Anwendungen weiterreichen.
Ein Problem: A/B-Tests
Facebook ändert ständig Kleinigkeiten an seinen Apps. Das muss nicht mal durch ein Update im Playstore geschehen, sondern kann im laufenden Betrieb sein. Die Änderungen sind auch nicht zwingend einheitlich, sondern können bei verschiedenen Nutzern unterschiedlich sein. Nennt sich A/B-Test.
In den letzten Wochen hat sich auf all meinen Geräten der „Teilen“-Button regelmäßig verändert:
- Manchmal war er im „Senden“-Menü unterhalb des Beitrags versteckt. Dann heißt er
Teilen via…
. - Manchmal war er im Overflow-Menü (die drei Punkte rechts oberhalb des Beitrags) untergebracht. Dann lautet die Bezeichnung
Teile Link…
. - Und von Zeit zu Zeit entfernt Facebook den Button komplett, dann besteht keine Möglichkeit mehr Tuck zu nutzen.
Über den Sinn und Unsinn der sich dahinter verbirgt kann man nur spekulieren. Für mich steht jedoch fest: Die Variante den Download über den Android-Share-Intent anzustoßen ist die einzig saubere. Die Alternativen wären, den Link zu kopieren und händisch in eine App einzufügen was mir zu umständlich und zeitaufwändig in der Nutzung ist, bzw. eine App zu schreiben, die im Hintergrund die Zwischenablage auf Instagram-Links überwacht, was auf neueren Android-Versionen nur durch Hacks machbar ist.
Falls die Teilen-Funktion also mal wieder entfernt wurde warte ich einfach geduldig ab. Erfahrungsgemäß kommt sie früher oder später wieder.
Tuck setzt also beim Share-Intent an.
Im Manifest ist deklariert, dass text/plain
angenommen werden kann:
<intent-filter>
<action android:name="android.intent.action.SEND"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="text/plain"/>
</intent-filter>
Damit taucht Tuck in der Liste der Apps auf, wenn in Instagram ein Beitrag geteilt wird.
Jetzt, da Tuck den Auftrag erhalten hat, ist nichts weiter zu tun als im Hintergrund den Download zu starten und den Nutzer dezent über den Status zu informieren. Dies geschieht in Form von Benachrichtigungen in der Statusleiste. Somit ist gewährleistet, dass Instagram nicht verlassen wird und ungestört weiter genutzt werden kann.
Teil B: Foto herunterladen
Was geschieht aber eigentlich genau im Hintergrund?
Tuck erhält einen Link in der Form https://www.instagram.com/p/BrncKOOB83d?utm_source=ig_share_sheet&igshid=1jd5d0ezsv5ke
und entnimmt daraus den Shortcode BrncKOOB83d
. Damit kann nun die REST-API https://www.instagram.com/p/{shortcode}/?__a=1
bedient werden, die alle relevanten Informationen zu einem Beitrag liefert:
{
"graphql": {
"shortcode_media": {
"shortcode": "Bt3rVNXAUdb",
"dimensions": {
"height": 1080,
"width": 1080
},
"display_url": "https://scontent-vie1-1.cdninstagram.com/vp/32ab13080e6a4e7011032b10baa6098b/5D1201E2/t51.2885-15/e35/50693385_1964945376887379_6676587143203401251_n.jpg?_nc_ht=scontent-vie1-1.cdninstagram.com",
"owner": {
"id": "728187757",
"is_verified": true,
"username": "danielkordan",
...
},
...
Besonders interessant ist display_url
, da es die URL zum Foto in der maximalen Auflösung liefert.
Erfahrungen
Wer sich für die genaue Implementierung interessiert kann den Code auf GitHub betrachten.
Ich möchte aber noch ein paar Dinge hervorheben, auf die ich während der Entwicklung der App gestoßen bin:
- Zum Parsen wird Moshi zusammen mit Retrofit verwendet. Eine tolle moderne Alternative zu Jackson oder GSON. Damit lässt sich auch kinderleicht das Problem lösen, dass je nach Beitragstyp (Einzelbild oder Beitrag mit mehreren Bildern) die JSON-Response unterschiedlich aussieht: PostAdapter.kt
- Die App ist in Kotlin geschrieben. Dies bringt viele Vorteile mit sich. Besonders gut sind die Data Classes, die prädestiniert dafür sind POJOs für die JSON-Deserialisierung zu erzeugen.
- Die Anzeige der Notifications hat einiges an Kopfzerbrechen und Ärger bereitet. Die API erscheint mir nicht sehr gelungen und sehr stark zwischen verschiedenen Android-Versionen fragmentiert. Außerdem existieren Bugs ohne die Aussicht auf Korrektur.
- Praktisch überall wo Netzwerkaufrufe (hier: REST-API, Downloads) stattfinden, verwende ich RxJava/RxAndroid. Die Integration in Retrofit ist genial einfach und sicher.