Retrofit: synchronní a asynchronní požadavky

Minule jsme si ukázali, jak použít Retrofit v Android studiu. Dnes se podíváme na synchronní a asynchronní požadavky.

Synchronní požadavek

Při synchronním požadavku, je na server odeslán požadavek, aplikace se „zastaví“ a čeká, dokud neobdrží odpověď od serveru.

Asynchronní požadavek

Při asynchronním požadavku se používají tzv. callbacky. Na server se odešle požadavek (stejně jako při synchronním požadavku) ale nečeká se na odpověď a kód pokračuje dál. Na odpověď ze serveru čeká callback, který provede požadovanou operaci s odpovědí od serveru. Tento callback se může zavolat kdykoliv (odpověď od serveru trvá různě dlouho).

V kódu je vidět asynchronní volání, které je shodné jako v prvním seznámení s Retrofit, přibylo synchronní volání:

package cz.vencax.retrofit;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;

import java.io.IOException;

import cz.vencax.retrofit.model.Book;
import cz.vencax.retrofit.model.Books;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        final Button button = findViewById(R.id.buttonAsynchronous);
        button.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                getBooksFromApiAsynchronous();
            }
        });

        final Button buttonPost = findViewById(R.id.buttonSynchronous);
        buttonPost.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                try {
                    getBooksFromApiSynchronous();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    /**
     * Asynchronous Retrofit request
     */
    private void getBooksFromApiAsynchronous() {

        Log.d("xxx", "getBooksFromApiAsynchronous");

        BookService bookService = RetrofitClient.getClient().create(BookService.class);
        Call<Books> books = bookService.getAllBooks();
        books.enqueue(new Callback<Books>() {

            @Override
            public void onResponse(Call<Books> call, Response<Books> response) {
                Log.d("xxx", "Asynchronous onResponse: " + response);
                if (response.isSuccessful()) {
                    Log.d("xxx", "Asynchronous response.isSuccessful()" + response.body());
                    Books books = response.body();

                    Log.d("xxx", "Asynchronous response.isSuccessful(), books count: " + books.getBooks().size());
                    for (Book book : books.getBooks()) {
                        Log.d("xxx", "Asynchronous Book: " + book.getTitle() + ", " + book.getAuthor());
                    }
                } else {
                    Log.d("xxx", "Asynchronous response NOT isSuccessful()" + response.errorBody().source());
                }
            }

            @Override
            public void onFailure(Call<Books> call, Throwable t) {
                Log.d("xxx", "Asynchronous onFailure: " + t);
            }
        });
    }

    /**
     * Synchronous Retrofit request
     */
    private void getBooksFromApiSynchronous() throws IOException {

        Log.d("xxx", "getBooksFromApiSynchronous");

        BookService bookService = RetrofitClient.getClient().create(BookService.class);
        Call<Books> call = bookService.getAllBooks();
        Books books = call.execute().body();

        Log.d("xxx", "Synchronous response.isSuccessful(), books count: " + books.getBooks().size());
        for (Book book : books.getBooks()) {
            Log.d("xxx", "Synchronous Book: " + book.getTitle() + ", " + book.getAuthor());
        }
    }
}

 

 

Android se nepřipojuje k WiFi síti bez internetu

Android od verze KitKat (4.4) se automaticky nepřipojuje k WiFi sítím, u kterých nezjistí dostupnost internetu. Asi to dává smysl, proč by se pojil k síti, která nemá internet? Raději zkusí jinou síť kde internet bude. Ale jak to vyřešit pokud chceme aby s k této síti připojoval? Uděláme malý hack, kterým nasimulujeme servery, na kterých Android zjišťuje, že je na internetu 🙂 Android po připojení zkontroluje URL (je natvrdo zadrátovaná v Androidu a může se měnit v závislosti na verzi Androidu). Pokud dostane zpět požadovanou odpověď, považuje WiFi síť za připojenou k internetu (i když internet nemá) 🙂

Požadavky na danou adresu jsou klasickým GETem a zařízení požaduje odpověď 204 (No content) to je celé. Jenže jak si tyto servery nasimulovat?

Nejjednodušší je v DNS překládat URL (clients3.google.com a connectivitycheck.gstatic.com) na náš server, kde spustíme nginx s touto konfigurací:

vim /etc/nginx/sites-enabled/default
# ====================================
# = android internet hack
# ====================================

server {
    server_name
        clients3.google.com
        connectivitycheck.gstatic.com;
    listen 80;

    location /generate_204 {
        return 204;
    }
}

 

Android: zpřístupnění nově vytvořeného souboru přes USB v počítači

Řeším zajímavý úkol: vytvoř v Android zařízení soubor, který bude po připojení telefonu k PC vidět v PC a bude možné jej překopírovat z telefonu do PC.

Dejte si pozor, protože od Android 5 musíte jednotlivá oprávnění povolovat na vyžádání, my budeme potřebovat oprávnění pro ukládání na úložiště. Pro jednoduchost vynechám kód nutný pro získání oprávnění zápisu do úložiště.

Do AndroidManifext.xml přidáme oprávnění:

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

A jdeme na zdrojový kód (nezapomeňte kontrolovat oprávnění zápisu na úložiště – od SDK Android 5!):

//vytvorime slozku do ktere budeme pridavat soubory
//slozka bude v "rootu" uloziste a jmenuje se xml
File rootFolder = new File(Environment.getExternalStorageDirectory(), "xml");
if (!rootFolder.exists()) {
   rootFolder.mkdir();
}

try {
   //vytvorime soubor ktery bude pristupny v pocitaci
   File file = new File(rootFolder , "xml" + getCurrentTimeStampForFile() + ".xml");

   FileWriter writer = new FileWriter(file);
   try {
      writer.write("obsah souboru");
   } finally {
      writer.close();
   }

   //toto je dulezite, timto prikazem se soubor zviditelni a bude po pripojeni telefonu pres USB videt v pocitaci
   MediaScannerConnection.scanFile(mContext, new String[] {file.toString()}, null, null);

} catch (Exception e) {
   //problem
}

Nejdůležitější příkaz je:

MediaScannerConnection.scanFile(mContext, new String[] {file.toString()}, null, null);

Tímto příkazem se soubor zviditelní mezi ostatními soubory a bude možné ho překopírovat do počítače

Debian 9 (stretch) – instalace php 7.1

V poslední verzi Debianu Stretch je defaultně php 7.0, pokud potřebujete novější 7.1, je možné jej bez kompilace zdrojových kódů nainstalovat přes balíčky:

apt-get install apt-transport-https lsb-release ca-certificates
wget -O /etc/apt/trusted.gpg.d/php.gpg https://packages.sury.org/php/apt.gpg
echo "deb https://packages.sury.org/php/ $(lsb_release -sc) main" > /etc/apt/sources.list.d/php.list
apt-get update
apt-get install php7.1

Stejným způsobem nainstalujeme PHP7.2:

apt install php7.2 php7.2-cli php7.2-common php7.2-json php7.2-opcache php7.2-mysql php7.2-zip php7.2-fpm php7.2-mbstring 

Bower + Grunt + Colorbox

Dnes vám naznačím jak do projektů přidávám knihovny třetích stran  a jak spojuji css a js soubory, které následně minifukuji. Ukážeme se to na pluginu Colorbox který používám pro zobrazení detailu fotek v pop-up okně.

Nainstalujeme Colorbox přes bower:

bower install jquery-colorbox --save-dev

Grunt task pro Colorbox

Pro zkopírování souborů používám balíček grunt-contrib-copy a pro minifikaci css do jednoho souboru grunt-contrib-cssmin:

npm install grunt-contrib-copy --save-dev
npm install grunt-contrib-cssmin --save-dev
npm install grunt-contrib-concat --save-dev

Nejdůležitější část souboru Gruntfile.js:

        copy: {
            colorbox: {
                expand: true,
                flatten: true,
                src: './bower_components/jquery-colorbox/example3/images/*',
                dest: 'www/css/images'
            }
        },
        cssmin: {
            target: {
                files: {
                    'www/css/main.min.css': [
                        'www/css/main.css',
                        'bower_components/jquery-colorbox/example3/colorbox.css',
                    ]
                }
            }
        },
        concat: {
            js: {
                src: ['bower_components/jquery/dist/jquery.min.js',
                    'bower_components/bootstrap/dist/js/bootstrap.min.js',
                    'bower_components/jquery-colorbox/jquery.colorbox-min.js',
                    'www/js/main.js'
                ],
                dest: 'www/js/compiled.min.js'
            }
        },

Tím se nakopírují ohrají obrázky obrázky z ./bower_components/jquery-colorbox/example3/images/ do www/css/image, soubor bower_components/jquery-colorbox/example3/colorbox.css se zmnifikuje a připojí k souboru www/css/main.css a soubor bower_components/jquery-colorbox/jquery.colorbox-min.js připojíme k zminifikovanému souboru www/js/compiled.min.js

Do souboru s JS přidáme inicializaci Colorboxu (v mém případě main.js):

$('.colorbox').colorbox({rel:'gal'});

Parametr {rel:’gal‘} označuje že se jedná o galerii. Pokud bude více odkazů s class=“colorbox“ půjde mezi fotkami přepínat a nebude se muset pop-up okno zavírat.

Odkazu s obrázkem přidáme classu colorbox:

<a href="{$pubPhoto->photo}" class="colorbox">
   <img src="{$pubPhoto->photoSmall}" alt="{$pubPhoto->title}">
</a>

A je to 🙂

Android: jak na responzivní zobrazení komponent na celou výšku displeje

Pokud budete chtít zobrazit komponenty rovnoměrně na celou výšku displeje, můžete využít ConstraintLayout. Jedná se o nový layout, který je dostupný v Android Studiu. Nezapomeňte ošetřit malé displeje, aby se komponenty na displej vešly.

constraint-layout

Kód pro inspiraci:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="cz.vencax.constraintlayout.MainActivity">

    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!1"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/textView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="TextView2"
        app:layout_constraintBottom_toBottomOf="@+id/textView3"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="@+id/textView1"
        app:layout_constraintVertical_bias="0.5" />

    <TextView
        android:id="@+id/textView3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="TextView3"
        app:layout_constraintBottom_toBottomOf="@+id/textView4"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="@+id/textView2"
        app:layout_constraintVertical_bias="0.5" />

    <TextView
        android:id="@+id/textView4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="TextView4"
        app:layout_constraintBottom_toBottomOf="@+id/textView5"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="@+id/textView3"
        app:layout_constraintVertical_bias="0.5" />

    <TextView
        android:id="@+id/textView5"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="TextView5"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent" />

</android.support.constraint.ConstraintLayout>

 

WordPress jak na shortcode

Namotivován ze včerejší WPkonference sedám ke stroji a jdu vylepšit můj plugin pro fotogalerii rajče. Plugin nyní zobrazuje naposledy vytvořené galerie s úvodní fotkou. Rozhodl jsem se použít shortcode pro zobrazení celé galerie.

Shortcode je příkaz který na svém místě zavolá funkci a zobrazí html kód. Například následující shortcode by zobrazil fotogalerii [forogalerie]. Shortcode můžeme používat ve stránce i příspěvku.

Jak použít shortcode

Pro zpracování takto definovaného shorcode:

[rajce-galerie url="http://sdh-zabori.rajce.idnes.cz/Stedry_den_v_hospode_20.12.2015/"]

Použijeme nejjednodušší metodu:

// [rajce-galerie id="123" size="medium"]
function rajceGalleryFunc( $atts ) {
    $attsArray = shortcode_atts( array(
        'url' => NULL
    ), $atts );

    $return = "<div class=\"rajce-gallery\">";

    if($attsArray['url'] != "") {
       $return.= "RAJCE GALERIE URL: {$a['url']}";
    }

    $return.= "</div>";

    return $return;
}
add_shortcode( 'rajce-galerie', 'rajceGalleryFunc' );

Příkazem add_shorcode definujeme nový shortcode. První parametr označuje název shortoce ( [rajce-galerie … ) a druhý parametr je název funkce, která se zavolá pro tento shortcode.

Ve funkci si můžeme sáhnout na parametry uvedené u shortcode, kde definujeme defaultní hodnotu, pokud parametr není uveden. V mím případě NULL:

    $attsArray = shortcode_atts( array(
        'url' => NULL
    ), $atts );

Pak už si s parametry můžeme dělat co chceme 🙂

Jak publikovat Windows 10 aplikaci do Store

Povedlo se mi publikovat mou první aplikaci ve Windows Store 🙂 Jedná se o můj klasický HelloWorld 🙂 https://www.microsoft.com/cs-cz/store/apps/motor-cb/9nblggh4v9ft

Tady jsou mé poznámky a poznatky k celému procesu.

Nejprve je nutné se přihlásit Microsoftím účtem: https://dev.windows.com/cs-cz

V řídícím panelu klikneme na tlačítko Vytvořit novou aplikaci :  (https://dev.windows.com/cs-cz/registration/AccountInfo). Při prvním spuštění toho kroku je nutné vyplnit údaje o autorovi aplikace a zaplatit jednorázový poplatek 365,-Kč (částka se ještě navýší o daň = cca 77,-Kč). Celkem tedy 442Kč. Platbu je možné provést přes kreditní kartu nebo PayPal.

Vytvořenou UWP (Universal Windows Platform) aplikaci a vyexportujeme ji přímo z Visual Studia. Klikneme na projekt pravým tlačítkem a vybereme Store ->Create App Packages … :

store1

store2

Poté je ještě nutné spustit App Certification Kit – jedná se o testy, které se samy vykonají a otestují základní funkčnost aplikace. Po dokončení těchto kroků máme k dispozici soubor s aplikací:

Documents\Visual Studio 2015\Projects\MotorCB\MotorCB\AppPackages\MotorCB_1.1.6.0_x86_x64_arm_bundle.appxupload

Ten nahrajeme na https://developer.microsoft.com/cs-cz/dashboard/overview a doplníme povinné údaje.

store3

A pak už jen čekat 🙂 Mou první aplikaci schválili za necelé 2 dny. Při tomto procesu se opět na aplikaci spouští testy.

Každý další upgrade aplikace je většinou schválen do 24hodin a do dalších 24 hodin se změny projeví ve Windows Store.

store4

Záseky při publikování aplikací

Šipka zpět

Tohle jsem nepochopil, ale programátor si musí ošetřit funkčnost šipky zpět 🙂 Úprava spočívá v přidání těchto řádků:

 namespace MotorCB
 {
     /// <summary>
     /// Provides application-specific behavior to supplement the default Application class.
     /// </summary>
     sealed partial class App : Application
     {
         /// <summary>
         /// Initializes the singleton application object.  This is the first line of authored code
         /// executed, and as such is the logical equivalent of main() or WinMain().
         /// </summary>
         public App()
         {
             Microsoft.ApplicationInsights.WindowsAppInitializer.InitializeAsync(
                 Microsoft.ApplicationInsights.WindowsCollectors.Metadata |
                 Microsoft.ApplicationInsights.WindowsCollectors.Session);
             this.InitializeComponent();
             this.Suspending += OnSuspending;
         }
 
         /// <summary>
         /// Invoked when the application is launched normally by the end user.  Other entry points
         /// will be used such as when the application is launched to open a specific file.
         /// </summary>
         /// <param name="e">Details about the launch request and process.</param>
         protected override void OnLaunched(LaunchActivatedEventArgs e)
         {
 #if DEBUG
             if (System.Diagnostics.Debugger.IsAttached)
             {
                 this.DebugSettings.EnableFrameRateCounter = false;
             }
 #endif
             Frame rootFrame = Window.Current.Content as Frame;
 
             // Do not repeat app initialization when the Window already has content,
             // just ensure that the window is active
             if (rootFrame == null)
             {
                 // Create a Frame to act as the navigation context and navigate to the first page
                 rootFrame = new Frame();
 
                 rootFrame.NavigationFailed += OnNavigationFailed;
+                rootFrame.Navigated += OnNavigated;
 
                 if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
                 {
                     //TODO: Load state from previously suspended application
                 }
 
                 // Place the frame in the current Window
                 Window.Current.Content = rootFrame;
+
+                // Register a handler for BackRequested events and set the
+                // visibility of the Back button
+                SystemNavigationManager.GetForCurrentView().BackRequested += OnBackRequested;
+
+                SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility =
+                    rootFrame.CanGoBack ?
+                    AppViewBackButtonVisibility.Visible :
+                    AppViewBackButtonVisibility.Collapsed;
             }
 
             if (e.PrelaunchActivated == false)
             {
                 if (rootFrame.Content == null)
                 {
                     // When the navigation stack isn't restored navigate to the first page,
                     // configuring the new page by passing required information as a navigation
                     // parameter
                     rootFrame.Navigate(typeof(MainPage), e.Arguments);
                 }
                 // Ensure the current window is active
                 Window.Current.Activate();
             }
         }
 
         /// <summary>
         /// Invoked when Navigation to a certain page fails
         /// </summary>
         /// <param name="sender">The Frame which failed navigation</param>
         /// <param name="e">Details about the navigation failure</param>
         void OnNavigationFailed(object sender, NavigationFailedEventArgs e)
         {
             throw new Exception("Failed to load Page " + e.SourcePageType.FullName);
         }
 
+        private void OnNavigated(object sender, NavigationEventArgs e)
+        {
+            // Each time a navigation event occurs, update the Back button's visibility
+            SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility =
+                ((Frame)sender).CanGoBack ?
+                AppViewBackButtonVisibility.Visible :
+                AppViewBackButtonVisibility.Collapsed;
+        }
+
         /// <summary>
         /// Invoked when application execution is being suspended.  Application state is saved
         /// without knowing whether the application will be terminated or resumed with the contents
         /// of memory still intact.
         /// </summary>
         /// <param name="sender">The source of the suspend request.</param>
         /// <param name="e">Details about the suspend request.</param>
         private void OnSuspending(object sender, SuspendingEventArgs e)
         {
             var deferral = e.SuspendingOperation.GetDeferral();
             //TODO: Save application state and stop any background activity
             deferral.Complete();
         }
+
+
+        private void OnBackRequested(object sender, BackRequestedEventArgs e)
+        {
+            Frame rootFrame = Window.Current.Content as Frame;
+
+            if (rootFrame.CanGoBack)
+            {
+                e.Handled = true;
+                rootFrame.GoBack();
+            }
+        }
     }
 }

Otevření URL ve webovém prohlížeči

String url = "http://blog.venca-x.cz"
await Launcher.LaunchUriAsync(new Uri(url));

 

Android: layout pro různá zařízení

Pokud si přejete zobrazit různý layout pro telefon, 7″ tablet a 10″ tablet určitě vám pomohou tyto poznámky.

Starý a již nepreferovaný způsob:

  • layout výchozí složka – v té máte základní layout
  • layout-large pro 7″ tablet (funguje pro emulátor Nexus 7)
  • layout-xlarge pro 10″ tablet (funguje pro emulátor Galaxy Tab 10.1)

Nový preferovaný způsob je vybrání layoutu podle hodnoty dp

Jednotka dp (density independent piuxel) vyjadřuje fyzickou velikost displeje. a je definován vztahem:

1dp = 160px/dpi
  • 320dp: typické rozlišení pro telefon (240×320 ldpi, 320×480 mdpi, 480×800 hdpi, atd.).
  • 480dp: phablet – něco mezi telefonem a tabletem (480×800 mdpi).
  • 600dp: 7” tablet (600×1024 mdpi).
  • 720dp: 10” tablet (720×1280 mdpi, 800×1280 mdpi, atd)

Šablony pro zůzně velká zařízení

  • sw600dp = nejmenší rozměr (smallestWidth) 600dp – použije se pokud nejmenší rozměr displeje je alespoň 600dp – bez ohledu na to zda je to šířka nebo výška
  • w600dp = šířka displeje 600dp
  • h600dp = výška displeje 600dp

Příklad: Nexus 6 se má velikost displeje 5,96″ a Quad HD v rozlišení 2560 x 1440 (493 ppi). To odpovídá ~ 730 x 410 dp

Vaše zařízení můžete dohledat na adrese http://www.emirweb.com/ScreenDeviceStatistics.php

Typická zařízení

  • Nexus 7: 1280 x 800 px (961 x 600 dp) / tvdpi / Large screen
  • Nexus 10: 2560 x 1600 px (1280 x 800 dp) / xhdpi / XLarge screen

Pokud tedy chci layout pro Nexus 7, založím ve složce app\src\main\res\layout-w600dp\ požadovaný layout

Pro Nexus 10 založím v app\src\main\res\layout-w720dp\ požadovaný layout