Android Espresso – první jednoduchý test

Testujete? Já se o to pokouším. Pro komplexnější testování UI prvků aplikace doporučuji nástroj Espresso.

Pokud vytvoříte v Android Studiu nový projekt, máte základ připraven. Android Studio vytvoří základní strukturu a základní testy automaticky. Jedná se o složky:

  • androidTest – do této složky dáváme UI testy. Používáme: Espresso a JUnit
  • test – do teto složky dáváme Unit testy, pro tyto testy používáme: Mockito, Robolectric a JUnit

Naše Espresso testy tedy budeme umísťovat do složky androidTest. Pro první test stačí do souboru build.gradle (ve složce app) přidat závislost:

androidTestImplementation 'com.android.support.test:rules:1.0.2'

Můj build.gradle vypadá takto:

apply plugin: 'com.android.application'

android {
compileSdkVersion 28
defaultConfig {
applicationId "cz.vencax.hellobuild"
minSdkVersion 15
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}

dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
androidTestImplementation 'com.android.support.test:rules:1.0.2'
}

Pojďme vytvořit náš první Espresso test. Otestujeme, zda TextView obsahuje text Hello World!

package cz.vencax.hellobuild;

import android.support.test.filters.LargeTest;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;

import org.junit.After;
import org.junit.Before;
import org.junit.FixMethodOrder;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.MethodSorters;

import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.containsString;

@RunWith(AndroidJUnit4.class)
@LargeTest
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class FirstTest {

@Rule
public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(MainActivity.class);

@Before
public void setUp() throws Exception {
//Before Test case execution
}

@Test
public void test1ChatId() {
//onView(withId(R.id.helloTextView)).check(matches(isDisplayed()));
onView(withId(R.id.helloTextView)).check(matches(withText(containsString("Hello World!"))));
}

@After
public void tearDown() throws Exception {
//After Test case Execution
}
}

Test spustíme kliknutím pravým tlačítkem na soubor s testem v levé části Android Studia – Project. Nebo z příkazové řádky:

gradlew connectedAndroidTest

Azure DevOps – private agent pro Android build

Azure DevOps je sklělý nástroj pro vývojáře. Nabízí ZDARMA soukromé git repozitáře, nástroj pro tasky (řízení práce), a build nástroje. To vše “zdarma”. Kompletně jsem na tuto platformu přešel a jsem maximálně spokojený. Android aplikace automaticky po pushi do Gitu otestuji, provedu build a vydám novou vývojovou verzi. Build agent má však omezení na běh max. 10h/měsíc, na malé projekty bohatě dostačuje. Pokud jste náročnější a tento limit vám nestačí, jednoduše nainstalujete na svůj server svého soukromého agenta, který provádí build. Pak jste omezeni pouze výkonem vašeho serveru. Pojďme si zprovoznit soukromého agenta pro build Android aplikace:

Stáhneme a nainstalujete Java SDK

Stáhneme a nainstalujeme Android Studio

Přidání proměnných do Path

Pro build je nutné přidat do path následující proměnné: java, JAVA_HOME, JDK, AndroidSDK
Jak zjistím kde mám nainstalován Android SDK abych ho přidal do Path?
Android Studio -> Menu -> Tools -> SDK Manager -> položka Android SDK Location.
Do Uživatelsky definovaných proměnných v mém případě přidáme Název proměnné: ANDROID_HOME a Hodnota proměnné: C:\Users\Test\AppData\Local\Android\Sdk

do Path stejným způsobem přidáme ještě tyto hodnoty:
java : C:\Program Files\Java\jdk-11.0.2
JAVA_HOME : C:\Program Files\Java\jdk-11.0.2
JDK : C:\Program Files\Java\jdk-11.0.2

Po přidání mi build ještě nefungoval, bylo nutné provést restart zařízení.

Pokus o ruční build

Pro build by v cmd mělo fungovat (spustíme v root android projektu):
gradlew assembleDebug  nebo gradlew assembleRelease

Jak nainstalovat agenta

Nejprve musíme vytvořit token pro autentizaci agenta. Přihlásíme se do TFS a vpravo nahoře klikneme na uživatele -> Security ->Personal access tokens -> New Token. Vytvoříme nový token, kterému nastavíme expiraci (jde na max 1 rok?) a oprávnění. Zobrazí se nám token, který si uschovejte, budeme ho potřebovat pro agenta.

Na URL https://dev.azure.com/{your_organization}/_admin/_AgentPool (v mém případě je {your_organization} venca-x). Zde vidíme seznam agentů a jejich stav. Pro vytvoření nového agenta klikneme na tlačítko Download agent. Vybereme cílovou platformu a nainstalujeme podle postupu. Po nainstalování není nutné spouštět agenta jako administrátor. Instalace probíhá přes Windows PowerShell.

Jak zobrazím mé agenty a jejich dostupnost?

Jděte na adresu:
https://dev.azure.com/{your_organization}/_admin/_AgentPool kde v sekci Default vidíte vaše soukromé agenty a jejich stav. Po kliknutí na Capabilities vidíte všechny vlastnosti daného agenta. Zde je důležité aby byly vlastnosti:
java, JAVA_HOME, JDK, AndroidSDK

Google Home – příkazy

I přesto, že Google slíbil, že jeho asistentka bude v roce 2018 umět česky, zatím jsme se nedočkali. I přesto je skvěle použitelný a dokáže dělat domácího sluhu.

Několik nejznámějších příkazů pro Google Home. Všechny příkazy začínáme frází OK google nebo Hey google.

Play some music
Play some by Shakira
Set the volume to 70%
Next song
Stop
Pause
Play mamka playlist on Spotify

How far away is the moon?
What’s the weather tomorrow in Czech Republic?
What’s the weather today?
What’s the latest news?

How is the S&P500 doing?
How many euros are in a Canadian dollar?

Tell my about my day
Add butter to my shopping list

Set a timer for 15 minutes
Coll Mom

What can you do?

How many calories are in an apple?
How do you say ‘nice to meet you’ in Korean?
How do I say good morning in French
What’s 25 times 83?
What is the capital of Spain?
What is the radius of the sun?

What is the nearesr pharmacy?
When do they close?
What is their phone number?
Where is the nearest flower store?

Play how to cook steak videos on TV
Pause the Living Room speaker

Dim the lights in the kitchen
Is the light on in Tiler’s room?

Tell me a joke
Tell me a fun fact
I’m bored
What sound does a whale make?
What sound does a cow make?

Ok Google, Show me on TV
Show me photos of (Person) on TV
Show me photos of (Place) on TV
Show me photos of (Thing) on TV


Gson: parsrování lokálního json souboru na objekty

Dnes si ukážeme jednoduchý příklad jak parsrovat lokální .json na objetky. Na tuto práci je perfektní knihovna gson, přidáme ji mezi závislosti:

compile 'com.google.code.gson:gson:2.8.5'

Náš ukázkový file.json vypadá následovně:

{
  "list": [
    {
      "name": "Faraz Khonsari",
      "age": 24
    },
    {
      "name": "John Snow",
      "age": 28
    },
    {
      "name": "Alex Kindman",
      "age": 29
    }
  ]
}

Jedná se o list objektů. Jednoduché. Uděláme si objekt, který namapuje list jako ArrayList, ve kterém budou objekty s propertou name a age:

public class MyModel {

   @SerializedName("list")
   public ArrayList<MyObject> list;

   static public class MyObject {

      @SerializedName("name")
      public String name;

      @SerializedName("age")
       public int age;
   }
}

Načteme soubor:

public String inputStreamToString(InputStream inputStream) {
   try {
      byte[] bytes = new byte[inputStream.available()];
      inputStream.read(bytes, 0, bytes.length);
      String json = new String(bytes);
      return json;
   } catch (IOException e) {
      return null;
   }
}

Přečteme obsah souboru:

String myJson=inputStreamToString(mActivity.getResources().openRawResource(R.raw.file));

Obsah souboru konvertujeme na objekt:

MyModel myModel = new Gson().fromJson(myJson, MyModel.class);

 

Android: problém se závislostmi

Zobrazuje se vám taková to hláška a nevíte jak problém vyřešit?

Zkuste nejprve aktualizovat všechny balíčky na nejnovější verzi. Pokud problém přetrvává, zkuste následující postup:

V root složce problémového projektu spustit příkaz:

gradlew app:dependencies > out.txt

V souboru out.txt najít řádky, které nemají šipku na nastavenou verzi SDK (u mne 28):

com.android.support:exifinterface:27.1.0
com.android.support:support-v4:26.1.0

Správně má být takhle:

com.android.support:support-v4:26.1.0 -> 28.0.0

tyto řádky se správnou verzí sdk přidat do build.gradle:

implementation 'com.android.support:exifinterface:28.0.0'
implementation 'com.android.support:support-v4:28.0.0'

 

Android: RecyclerView

LisView je mrtvý, ať žije RecyclerView. Ano, je to opravdu tak, zapomeňte na ListView a začněte používat RecyclerView – je výkonější, flexibilnější a vyvýjí se, ListView je mrtvý.

Pojďme si udělat jednoduchou ukázku.

Do souboru build.gradle je nutné přidat závislost:

dependencies {
    implementation 'com.android.support:recyclerview-v7:28.0.0'
}

1) Přidání RecyclerView do šablony activity_main.xml:

<?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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/my_recycler_view"
        android:scrollbars="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</android.support.constraint.ConstraintLayout>

2) Model

Vytvoříme třídu s Movie.java s gettery a settery.

package cz.vencax.recyclerview.model;

public class Movie {
    private String title, genre, year;

    public Movie() {
    }

    public Movie(String title, String genre, String year) {
        this.title = title;
        this.genre = genre;
        this.year = year;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String name) {
        this.title = name;
    }

    public String getYear() {
        return year;
    }

    public void setYear(String year) {
        this.year = year;
    }

    public String getGenre() {
        return genre;
    }

    public void setGenre(String genre) {
        this.genre = genre;
    }
}

3) Adapter

Adapter slouží pro zobrazení dat v RecyclerView stejně jako v ListView. Jsou pouze přetížené jiné metody.

Vytvořte třídu s názvem MoviesAdapter.java. Zde metoda onCreateViewHolder () použije movie_list_row.xml. V metodě onBindViewHolder () jsou nastaveny příslušné údaje o filmu (titul, žánr a rok) pro každý řádek.

package cz.vencax.recyclerview.adapter;

import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import java.util.List;

import cz.vencax.recyclerview.R;
import cz.vencax.recyclerview.model.Movie;

public class MoviesAdapter extends RecyclerView.Adapter<MoviesAdapter.MyViewHolder> {

    private List<Movie> moviesList;

    public class MyViewHolder extends RecyclerView.ViewHolder {
        public TextView title, year, genre;

        public MyViewHolder(View view) {
            super(view);
            title = (TextView) view.findViewById(R.id.title);
            genre = (TextView) view.findViewById(R.id.genre);
            year = (TextView) view.findViewById(R.id.year);
        }
    }


    public MoviesAdapter(List<Movie> moviesList) {
        this.moviesList = moviesList;
    }

    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View itemView = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.movie_list_row, parent, false);

        return new MyViewHolder(itemView);
    }

    @Override
    public void onBindViewHolder(MyViewHolder holder, int position) {
        Movie movie = moviesList.get(position);
        holder.title.setText(movie.getTitle());
        holder.genre.setText(movie.getGenre());
        holder.year.setText(movie.getYear());
    }

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

4) Layout pro řádek v RecyclerView

Vytvořte layout xml s názvem movie_list_row.xml s níže uvedeným kódem. Tento layout vykreslí jeden řádek v RecyclerView se zobrazením názvu filmu, žánru a roku vydání.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="?android:attr/selectableItemBackground"
    android:clickable="true"
    android:focusable="true"
    android:orientation="vertical"
    android:paddingBottom="10dp"
    android:paddingLeft="16dp"
    android:paddingRight="16dp"
    android:paddingTop="10dp">

    <TextView
        android:id="@+id/title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:textColor="#222222"
        android:textSize="16dp"
        android:textStyle="bold" />

    <TextView
        android:id="@+id/genre"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/title" />

    <TextView
        android:id="@+id/year"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:textColor="#999999" />

</RelativeLayout>

5) Activity

Nakonec upravíme MainActivity.java a provedeme níže uvedené změny. Metoda prepareMovieData () přidá vzorové data do zobrazení v seznamu.

package cz.vencax.recyclerview.activity;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;

import java.util.ArrayList;
import java.util.List;

import cz.vencax.recyclerview.R;
import cz.vencax.recyclerview.adapter.MoviesAdapter;
import cz.vencax.recyclerview.model.Movie;

public class MainActivity extends AppCompatActivity {

    private List<Movie> movieList = new ArrayList<>();
    private RecyclerView recyclerView;
    private MoviesAdapter mAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        recyclerView = (RecyclerView) findViewById(R.id.recycler_view);

        mAdapter = new MoviesAdapter(movieList);
        RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getApplicationContext());
        recyclerView.setLayoutManager(mLayoutManager);
        recyclerView.setItemAnimator(new DefaultItemAnimator());
        recyclerView.setAdapter(mAdapter);

        prepareMovieData();
    }


    private void prepareMovieData() {
        Movie movie = new Movie("Mad Max: Fury Road", "Action & Adventure", "2015");
        movieList.add(movie);

        movie = new Movie("Inside Out", "Animation, Kids & Family", "2015");
        movieList.add(movie);

        movie = new Movie("Star Wars: Episode VII - The Force Awakens", "Action", "2015");
        movieList.add(movie);

        movie = new Movie("Shaun the Sheep", "Animation", "2015");
        movieList.add(movie);

        movie = new Movie("The Martian", "Science Fiction & Fantasy", "2015");
        movieList.add(movie);

        movie = new Movie("Mission: Impossible Rogue Nation", "Action", "2015");
        movieList.add(movie);

        movie = new Movie("Up", "Animation", "2009");
        movieList.add(movie);

        movie = new Movie("Star Trek", "Science Fiction", "2009");
        movieList.add(movie);

        movie = new Movie("The LEGO Movie", "Animation", "2014");
        movieList.add(movie);

        movie = new Movie("Iron Man", "Action & Adventure", "2008");
        movieList.add(movie);

        movie = new Movie("Aliens", "Science Fiction", "1986");
        movieList.add(movie);

        movie = new Movie("Chicken Run", "Animation", "2000");
        movieList.add(movie);

        movie = new Movie("Back to the Future", "Science Fiction", "1985");
        movieList.add(movie);

        movie = new Movie("Raiders of the Lost Ark", "Action & Adventure", "1981");
        movieList.add(movie);

        movie = new Movie("Goldfinger", "Action & Adventure", "1965");
        movieList.add(movie);

        movie = new Movie("Guardians of the Galaxy", "Science Fiction & Fantasy", "2014");
        movieList.add(movie);

        mAdapter.notifyDataSetChanged();
    }
}

A máme hotovo 🙂

6) Přidání oddělovače RecyclerView

recyclerView.addItemDecoration(new DividerItemDecoration(this, LinearLayoutManager.VERTICAL));

recyclerView.setAdapter(mAdapter);

7) Přidání click listeneru

RecyclerView také nemá metodu OnItemClickListener pro kliknutí na položku. Musíte napsat svou vlastní třídu implementující RecyclerView.OnItemTouchListener. Vytvořte třídu s názvem RecyclerTouchListener.java:

package cz.vencax.recyclerview.utils;

import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;

public class RecyclerTouchListener implements RecyclerView.OnItemTouchListener {

    private GestureDetector gestureDetector;
    private ClickListener clickListener;

    public RecyclerTouchListener(Context context, final RecyclerView recyclerView, final ClickListener clickListener) {
        this.clickListener = clickListener;
        gestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
            @Override
            public boolean onSingleTapUp(MotionEvent e) {
                return true;
            }

            @Override
            public void onLongPress(MotionEvent e) {
                View child = recyclerView.findChildViewUnder(e.getX(), e.getY());
                if (child != null && clickListener != null) {
                    clickListener.onLongClick(child, recyclerView.getChildPosition(child));
                }
            }
        });
    }

    @Override
    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {

        View child = rv.findChildViewUnder(e.getX(), e.getY());
        if (child != null && clickListener != null && gestureDetector.onTouchEvent(e)) {
            clickListener.onClick(child, rv.getChildPosition(child));
        }
        return false;
    }

    @Override
    public void onTouchEvent(RecyclerView rv, MotionEvent e) {
    }

    @Override
    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {

    }

    public interface ClickListener {
        void onClick(View view, int position);

        void onLongClick(View view, int position);
    }
}

Nakonec přidáme tento objekt jako listener k RecyclerView v activity:

recyclerView.addOnItemTouchListener(new RecyclerTouchListener(getApplicationContext(), recyclerView, new RecyclerTouchListener.ClickListener() {
            @Override
            public void onClick(View view, int position) {
                Movie movie = movieList.get(position);
                Toast.makeText(getApplicationContext(), movie.getTitle() + " is selected!", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onLongClick(View view, int position) {

            }
        }));

 

Android: Cleartext HTTP traffic to downloads.bbc.co.uk not permitted

Od Android 8 (Oreo) je defaultně zakázáno komunikovat po http – je nutné komunikovat po https. Pokud stahujete data ze zdroje, kde nemůžete ovlivnit nasazení https, lze toto pravidlo obejít přidáním níže uvedeného kódu do vaší aplikace.

Vytvoříme soubor: res/xml/network_security_config.xml

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config cleartextTrafficPermitted="true" />
</network-security-config>

Do AndroidManifest.xm přidáme řádek: http://android:networkSecurityConfig=”@xml/network_security_config”

<application
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:networkSecurityConfig="@xml/network_security_config"
...

 

Debugování Retrofit požadavků

O tom, jak zprovoznit Retrofit jsme si již psali. Dnes se podíváme na to, jak získat více informací o tom, jak Retrofit funguje a jaké posílá a přijímá požadavky.

Začneme nainstalováním závislosti: com.squareup.okhttp3:logging-interceptor

implementation 'com.squareup.okhttp3:logging-interceptor:3.11.0'
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(Level.BASIC);
OkHttpClient client = new OkHttpClient.Builder()
    .addInterceptor(logging)
    .build();

Kde setLevel určuje úroveň logování: NONE, BASIC, HEADERS, BODY

Tím máme základní logování zprovozněno:

10-11 12:46:10.312 15337-15697/cz.vencax.mobilapp.mwmv2 D/OkHttp: --> POST https://vencax.local/mwm/api/login
10-11 12:46:10.312 15337-15697/cz.vencax.mobilapp.mwmv2 D/OkHttp: Content-Type: application/json; charset=UTF-8
10-11 12:46:10.312 15337-15697/cz.vencax.mobilapp.mwmv2 D/OkHttp: Content-Length: 107
10-11 12:46:10.312 15337-15697/cz.vencax.mobilapp.mwmv2 D/OkHttp: Authorization: Basic xxx
10-11 12:46:10.312 15337-15697/cz.vencax.mobilapp.mwmv2 D/OkHttp: {"actualDateTime":"2018-10-11T12:46:10.2580000+02:00","phoneId":"2c3c2a5fe5a7b529","versionNumber":"1.9.0"}
10-11 12:46:10.312 15337-15697/cz.vencax.mobilapp.mwmv2 D/OkHttp: --> END POST (107-byte body)

Pokud chcete logovat svým vlastním způsobem, můžete přetížit konstruktor:

HttpLoggingInterceptor logging = new HttpLoggingInterceptor(new 
Logger() {
@Override public void log(String message) {
    Log.d(TAG, "message: ");
    }
});

 

Jak stáhnout .apk z Google Play

Klient má Android zařízení, které jsou pouze v interní síti, bez přístupu na internet. Přál by si na tyto zařízení dostat aplikace z Google Play. Jde to?

Ano, jde 🙂 Z Google Play je možné stáhnout .apk soubor, který se nainstaluje na zařízení. Existují na to nejrůznější webové stránky – ty mi ovšem většinou nefungují. Nejlepší zkušenosti mám s doplňkem do Chrome, APK Downaloader. Stačí nainstalovat do Chrome na PC, v Chrome najít aplikaci, zkopírovat URL do pluginu a stáhnout

 

Android: fragment worker

Dnes se podíváme na to, jak dělat složitější operace ve fragmentu při které se bude volat metoda z activity. Zaměříme se na to, jak správně napsat kód aby aplikace nespadla při otočení displeje.

Jednoduchá ukázka jak to nedělat:

MainActivity.java – klasická activity, která má veřejnou metodu worker, která by v praxi vykonávala nějakou operaci. Nyní pro ukázku vypíše pouze text do logu.

package cz.vencax.mobilapp.dockstatesapplication;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

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

    public void worker(String text) {
        Log.d("xxx", "text: " + text);
    }
}

LoginFragment.java – jednoduchý fragment s tlačítkem. Po tapnutí na tlačítko, se spustí AsyncTask, který volá metodu worker z MainActivity

package cz.vencax.mobilapp.dockstatesapplication;

import android.content.Context;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;

public class LoginFragment extends Fragment {

    public LoginFragment() {
        // Required empty public constructor
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        //return inflater.inflate(R.layout.fragment_login, container, false);
        View view = inflater.inflate(R.layout.fragment_login, container, false);

        Button button = (Button) view.findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener()
        {
            @Override
            public void onClick(View v)
            {
                new LoginAndParseInspectionTask().execute();
            }
        });

        return view;
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////

    class LoginAndParseInspectionTask extends AsyncTask<String, String, Void> {
        @Override
        protected Void doInBackground(String... strings) {
            for(int i = 0; i < 1000000000; i++) {
                ((MainActivity)getActivity()).worker("run" + i);
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            return null;
        }
    }
}

Pokud tento kód spustíte, zjistíte, že funguje. Bohužel po otočení displeje aplikace spadne….

Jak na to lépe?

Správná cesta je vytvoření static interface TaskCallbacks ve fragmentu, který implementujeme v activity. Metodu worker následně voláme přes mCallbacks.worker(“run” + i);

MainActivity.java

package cz.vencax.mobilapp.dockstatesapplication;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;

public class MainActivity extends AppCompatActivity implements LoginFragment.TaskCallbacks {

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

    @Override
    public void worker(String text) {
        Log.d("xxx", "text: " + text);
    }
}

LoginFragment.java

package cz.vencax.mobilapp.dockstatesapplication;

import android.content.Context;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;

public class LoginFragment extends Fragment {

    static interface TaskCallbacks {
        void worker(String text);
    }

    private TaskCallbacks mCallbacks;

    public LoginFragment() {
        // Required empty public constructor
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        if (!(context instanceof TaskCallbacks)) {
            throw new IllegalStateException("Activity must implement the TaskCallbacks interface.");
        }

        // Hold a reference to the parent Activity so we can report back the task's
        // current progress and results.
        mCallbacks = (TaskCallbacks) context;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        //return inflater.inflate(R.layout.fragment_login, container, false);
        View view = inflater.inflate(R.layout.fragment_login, container, false);

        Button button = (Button) view.findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener()
        {
            @Override
            public void onClick(View v)
            {
                new LoginAndParseInspectionTask().execute();
            }
        });

        return view;
    }


    class LoginAndParseInspectionTask extends AsyncTask<String, String, Void> {
        @Override
        protected Void doInBackground(String... strings) {
            for(int i = 0; i < 1000000000; i++) {
                //((MainActivity)getActivity()).worker("run" + i);
                mCallbacks.worker("run" + i);
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            return null;
        }
    }
}

Teď již při otáčení displeje není problém.