User Tools

Site Tools


android

Linux environment

How to set up a headless build environment.

OSUbuntu 20.04
Android SDKAndroid 11 (API level 30) as of 26.11.2021

Install android-sdk on Ubuntu

# install openjdk8 - tested android sdk and its compatible
sudo apt-get install openjdk-8-jdk
# activate the 8th version if you have multiple using:
# sudo update-alternatives --config java


# install android-sdk under /usr/lib/android-sdk/
sudo apt update && sudo apt install android-sdk



# download the commandlinetools and add them to sdk
# <sdk>/cmdline-tools/latest/
wget -P /tmp/ https://dl.google.com/android/repository/commandlinetools-linux-6858069_latest.zip

# check https://stackoverflow.com/questions/60440509/android-command-line-tools-sdkmanager-always-shows-warning-could-not-create-se
sudo mkdir -p /usr/lib/android-sdk/cmdline-tools/

sudo unzip /tmp/commandlinetools-*.zip -d /usr/lib/android-sdk/cmdline-tools/
sudo mv /usr/lib/android-sdk/cmdline-tools/cmdline-tools/ /usr/lib/android-sdk/cmdline-tools/tools/

# open bashrc and modify the environment variables
vim ~/.bashrc
export ANDROID_HOME="/usr/lib/android-sdk/"
export PATH=$PATH:$ANDROID_HOME/cmdline-tools/tools/bin:$ANDROID_HOME/platform-tools

# apply changes in bashrc
source ~/.bashrc


# setting the rights 
sudo chown $USER:$USER $ANDROID_HOME -R



# can successfully access the sdkmanager
sdkmanager --version

# install the platform tools https://developer.android.com/studio/releases/platform-tools
sdkmanager "platform-tools" "platforms;android-29"

# install the build tools https://developer.android.com/studio/releases/build-tools
sdkmanager "build-tools;29.0.3"


# confirm all the licenses for the android tools otherwise they wont work
yes | sdkmanager --licenses


# check for android sdk updates
sdkmanager --update




# you can build android project now 
cd <YOUR_ANDROID_PROJECT>
./gradlew build




HOWTO

Plan app's Layout

  • use Fragments for top level elements which may be reused, since fragments can not be nested.
  • implement Views with custom Layout, implemented in xml to use these blocks on deeper levels.
    These views can be nested.
    implement layout in xml and assign the xml layout to the view (see Create a View class which will wrap the XML Layout)

Declaring custom XML attributes

An in detail describtion about declaring custom XML attributes for custom views can be found here: http://stackoverflow.com/questions/2695646/declaring-a-custom-android-ui-element-using-xml

<fc #FF0000>ACHTUNG:</fc>

  1. Nutze diesen Namespace, statt den der eigenen App:
    xmlns:app="http://schemas.android.com/apk/res-auto"

Fading Background

ListView

Select Item

 listView.setItemChecked(1, true);

Iterate Items

 				ContentListAdapter contentListAdapter = (ContentListAdapter) listView.getAdapter();
				
				for(int i=0; i<contentListAdapter.getCount(); i++){
					View v = (View) contentListAdapter.getItem(i);
					Log.d("View","Child is "+v );

Using JAR Libs

A Jar can be added to android, by putting it into the libs folder!

ACHTUNG:

Adroid kann nur JARs nutzen, die mit dem Compiler 1.7 gebaut wurden. Falls dies nicht der Fall ist - wird eine NoClassDefFound exception geworfen.

Tools

The tool monitor.bat can be used as a Standalone logger! Details are here

Aapt

Inspect the APK is done via

D:\Development\Eclipse Juno - Android\sdk\build-tools\android-4.2.2\aapt.exe dump badging theApp.apk

Unpackaging the APK is done via

Genimotion

Genimotion is a nice and fast Android VM.

It may support ARM Architecture, but this option must be enabled. How to do that is described here: http://forum.xda-developers.com/showthread.php?t=2528952

Maven

Dependncies

ACHTUNG: For some reason the dependencies form parent pom are not inherited to the child pom. So always add dependencies directly to children

Dependency to my own apklib:

The apklib

	<artifactId>barcode-lib-commons-dataproject</artifactId>
    <name>BarcodeLibCommonsDataProject</name>
    <version>1.0.0</version>
    <packaging>apklib</packaging>

The dependency

        <!-- Barcode Helper Lib  -->
        <dependency>
            <groupId>de.ivu.fare.apps-13.2.PIROL</groupId>
            <artifactId>barcode-lib-commons-dataproject</artifactId>
            <version>1.0.0</version>
            <type>apklib</type>
        </dependency>
Package type

The apk is an App.

    <artifactId>barcodelib2</artifactId>
    <version>1.0.0</version>
    <packaging>apk</packaging>
    <name>Barcode-Lib2</name>

The apklib - is the android lib project

    <artifactId>barcodelib2</artifactId>
    <version>1.0.0</version>
    <packaging>apklib</packaging>
    <name>Barcode-Lib2</name>
Signieren

Wie man Maven einrichtet, um eine signierte APK bauen zu können steth hier:
http://www.simpligility.com/2010/07/sign-zipalign-and-to-market-to-market-with-maven/

Danach kann eine signierte apk z.B. durch erzeugt werden:

mvn clean install
ZipAlign

TO do the zipalign - use the code as described here: http://stackoverflow.com/questions/10915922/how-to-check-that-maven-goal-called-during-phase

DO not forget the <skip>false</skip>

        <plugin>
            <groupId>com.jayway.maven.plugins.android.generation2</groupId>
            <artifactId>android-maven-plugin</artifactId>
            <version>3.2.0</version>
            <extensions>true</extensions>
            <configuration>
                <sdk>
                    <platform>8</platform>
                </sdk>
                <emulator>
                    <avd>2.3.3_API-10</avd>
                </emulator>
                <undeployBeforeDeploy>true</undeployBeforeDeploy>
                <assetsDirectory>${project.build.directory}/filtered-assets</assetsDirectory>
                <androidManifestFile>${project.build.directory}/filtered-manifest/AndroidManifest.xml</androidManifestFile>
                <zipalign>
                    <skip>false</skip>
                    <verbose>${build.verbosity}</verbose>
                    <inputApk>${project.build.directory}/${project.artifactId}-${build.version.name}.apk</inputApk>
                    <outputApk>${project.build.directory}/${project.artifactId}-${build.version.name}-aligned.apk</outputApk>
                </zipalign>
            </configuration>
            <executions>
                <execution>
                    <id>zipalign</id>
                    <phase>package</phase>
                    <goals>
                        <goal>zipalign</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
Release

Bauene in release mode, so dass die Variable BuildConfig.DEBUG==false ist: ist beim plugin android-maven-plugin am besten über einen Parameter -Dandroid.release=true steuerbar.

Die Details sind hier beschrieben: http://stackoverflow.com/questions/15055961/android-maven-plugin-disable-debug-build-for-apk

clean install -Dandroid.release=true

In-App Payments

Method Describtion Methods Transaction fee
Native Android Play In-App Payments Billed via Google Play Account. Service https://wallet.google.com is used. Credit Card (Visa)
Debit Card (Master Card)
30%

Certificates

Check Signature

jarsigner -verify -verbose -certs my_application.apk

The Certificate can be extracted from an APK. Just Exctract the File META-INF\ADT.RSA from the apk file, since an apk can be entered as a normal zip. Then use the following code to convert the ADT.RSA to the certificate.

openssl.exe pkcs7 -in ADT.RSA -print_certs -inform DER -out ADT.RSA.CER

Services

Services may run, after the app was stopped.

public class MyService extends IntentService {

	public MyService() {
		super("MyService");
	}

	@Override
	protected void onHandleIntent(Intent intent) { }



	/**
	 * Services has to use Handlers to toast messages
	 * @param context - the app context
	 * @param message - the message to toast
	 * @param handler - the handler to reuse
	 */
	public static void toastFromService(final Context context,
			final String message, Handler handler) {
		handler.post(new Runnable() {
			@Override
			public void run() {
				Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
				LOG.info("Service message: "+ message);
			}
		});
	}

        /** Start the instance of IntentService service by sending an Intent */
	public static final void start(Context context) {
		Intent intent = new Intent(context, MyService.class);
		context.startService(intent);
	}
	
        /** to toast stuff use  the handler */
	void toastMessage(final String message){
		handler.post(new Runnable() {
			@Override
			public void run() {
				Toast.makeText(getApplicationContext(), message, Toast.LENGTH_SHORT).show();
			}
		});
	}

Automatic Testing

  1. Create a new Test Project, as [http://developer.android.com/tools/testing/testing_android.html|stated here]]
android.test.ActivityInstrumentationTestCase2<MyActivityUnderTest>

Use this clss to test activities.

  • Every method in this class will be executed as a JUnitTest.
  • Before every test - the method setUp() is executed
  • test methods names have to start with “test”, or they won't be found

Espresso - UI tests

package digital.alf.geosound;

import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.CoreMatchers.allOf;

import android.Manifest;
import android.app.Activity;
import android.os.Bundle;

import androidx.navigation.NavController;
import androidx.navigation.Navigation;
import androidx.test.espresso.action.ViewActions;
import androidx.test.espresso.matcher.ViewMatchers;
import androidx.test.ext.junit.rules.ActivityScenarioRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import androidx.test.rule.GrantPermissionRule;

import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(AndroidJUnit4.class)
@LargeTest
public class JustOpenAllWindows {

    @Rule
    public ActivityScenarioRule<MainActivity> activityRule = new ActivityScenarioRule<>(MainActivity.class);

    @Rule
    public GrantPermissionRule mRuntimePermissionRuleFine = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION);

    @Rule
    public GrantPermissionRule mRuntimePermissionRuleCoarse = GrantPermissionRule.grant(Manifest.permission.ACCESS_COARSE_LOCATION);

    @Rule
    public GrantPermissionRule mRuntimePermissionRuleBackground = GrantPermissionRule.grant(Manifest.permission.ACCESS_BACKGROUND_LOCATION);


    @Test
    public void openPreferences() {

        activityRule.getScenario().onActivity(activity -> {
            Bundle args = new Bundle();
            NavController navController = Navigation.findNavController(activity, R.id.map);
            navController.navigate(R.id.action_fragment_maps_to_fragment_preferences, args);
        });

        onView(allOf(ViewMatchers.withText(R.string.dialog_preference_label), withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
                .check(matches(isDisplayed()));

        onView(ViewMatchers.withText(R.string.dialog_default_onout_volume_title))
                .perform(ViewActions.click());

        //Click on cancel button
        onView(ViewMatchers.withId(android.R.id.button2))
                .perform(ViewActions.click());


        onView(ViewMatchers.withText(R.string.dialog_default_onout_volume_title))
                .perform(ViewActions.click());

        //Click on cancel button
        onView(ViewMatchers.withId(android.R.id.button2))
                .perform(ViewActions.click());

        System.out.println("done");
    }

}

Execution in CodeBuild

Snippets

Preference Item with value in Summary

Declare an own xml attribute for custom Views. Add this to res/values/attrs.xml This attribute will contain the Mask for the summary, as known by String.format(mask, value)

    <declare-styleable name="PreferenceSummaryValue">
        <attr name="summaryFormat" format="string"/>
    </declare-styleable>

Use own attribute

        <de.ivu.eticket.app.gui.PreferenceSummaryValue
        android:key="deadline"
        android:defaultValue="12"
        android:title="The Deadline" 
        app:summaryFormat="end in %s days"/>

Implement own preference view

public class PreferenceSummaryValue extends EditTextPreference {
	
	private String summaryFormat = "%s";
	
	public PreferenceSummaryValue(Context context) {
		super(context);
		init(null, context);
	}
	
	public PreferenceSummaryValue(Context context, AttributeSet attrs) {
		super(context, attrs);
		init(attrs, context);
	}
	
	public PreferenceSummaryValue(Context context, AttributeSet attrs,
			int defStyle) {
		super(context, attrs, defStyle);
		init(attrs, context);
	}

	private void init(AttributeSet attributes, Context context){
		if(attributes != null){
			TypedArray tarr = context.obtainStyledAttributes(attributes, R.styleable.PreferenceSummaryValue);
			String attribute = tarr.getString(R.styleable.PreferenceSummaryValue_summaryFormat);
			if(attribute != null) summaryFormat = attribute;
			tarr.recycle();
		}
	}
	
	@Override
	public void setText(String text) {
		super.setText(text);
		setSummary(String.format(summaryFormat, text));
	}
	
	@Override
	public void setSummary(CharSequence summary) {
		super.setSummary(summary);
	}
	
	public void setSummaryFormat(String summaryFormat) {
		this.summaryFormat = summaryFormat;
	}
}

Layouts

TableLayout

  • Kann wirklich nur gleichmaeßige Zellen darstellen.
  • Kann kein RowSpan
  • Die Rows existieren als ein besonderes Objekt “TableRow”
  • Die Columns als besondere Objekte existieren nicht - es werden die CHildViews einfach gleich groß gehalten werden
  • Nur gleiche layouts sollten als Columns benutzt werden. TableLayout mit 1 TabeRow und LinearLayout als Table Column klappt gut.
  • Nutze Lieber GridLayout

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" xmlns:android="http://schemas.android.com/apk/res/android">

    <TableLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:stretchColumns="0" >

        <TableRow android:id="@+id/tableRow1" android:layout_width="match_parent" android:layout_height="wrap_content">
					
           			<TableLayout
           			    android:layout_width="match_parent"
           			    android:layout_height="wrap_content" >

            		    <TableRow android:id="@+id/tableRow1" android:layout_width="match_parent" android:layout_height="wrap_content">
            		        <TextView android:padding="5dp" android:id="@+id/textView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceMedium" android:text="Text"></TextView> </TableRow>
            		    
            		    <View android:background="@color/grey_light" android:layout_height="1px"></View>
            		    
            		    <TableRow android:id="@+id/tableRow1" android:layout_width="match_parent" android:layout_height="wrap_content"> 
            		        <TextView android:padding="5dp" android:id="@+id/textView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceMedium" android:text="Text"></TextView> </TableRow>
    				</TableLayout>
    				
           			<TableLayout
           			    android:layout_width="match_parent"
           			    android:layout_height="wrap_content" >

            		    <TableRow android:id="@+id/tableRow1" android:layout_width="match_parent" android:layout_height="wrap_content">
            		        <TextView android:padding="5dp" android:id="@+id/textView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceMedium" android:text="Text"></TextView> </TableRow>
            		    
            		    <View android:background="@color/grey_light" android:layout_height="1px"></View>
            		    
            		    <TableRow android:id="@+id/tableRow1" android:layout_width="match_parent" android:layout_height="wrap_content"> 
            		        <TextView android:padding="5dp" android:id="@+id/textView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceMedium" android:text="Text"></TextView> </TableRow>
    				</TableLayout>
    				
           			<TableLayout
           			    android:layout_width="match_parent"
           			    android:layout_height="wrap_content" >

            		    <TableRow android:id="@+id/tableRow1" android:layout_width="match_parent" android:layout_height="wrap_content">
            		        <TextView android:padding="5dp" android:id="@+id/textView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceMedium" android:text="Text"></TextView> </TableRow>
            		    
            		    <View android:background="@color/grey_light" android:layout_height="1px"></View>
            		    
            		    <TableRow android:id="@+id/tableRow1" android:layout_width="match_parent" android:layout_height="wrap_content"> 
            		        <TextView android:padding="5dp" android:id="@+id/textView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceMedium" android:text="Text"></TextView> </TableRow>
    				</TableLayout>

           			<TableLayout
           			    android:layout_width="wrap_content"
           			    android:layout_height="wrap_content" >

           			    <TableRow
           			        android:id="@+id/tableRow2"
           			        android:layout_width="wrap_content"
           			        android:layout_height="wrap_content" >

           			        <LinearLayout
           			            android:layout_width="wrap_content"
           			            android:layout_height="wrap_content"
           			            android:orientation="vertical" >

           			            <TextView
           			                android:id="@+id/textView1"
           			                android:layout_width="wrap_content"
           			                android:layout_height="wrap_content"
           			                android:padding="5dp"
           			                android:text="Text"
           			                android:textAppearance="?android:attr/textAppearanceMedium" />

           			        </LinearLayout>

           			    </TableRow>
           			</TableLayout>
					
                    
                </TableRow>
        		
        <!-- display this button in 3rd column via layout_column(zero based) -->

        <!-- display this button in 2nd column via layout_column(zero based) -->
    </TableLayout>

</LinearLayout>

GridLayout

  • Has row-span which tableLayout does not have
  • CAN NOT DISTRIBUTE HORIZONTAL SPACE e.g. when you need multiple columns which occupy the parent \\

How to use GridLayout is described in this video:

LinearLayout

  • Can order items in a row.
  • Children must have either a given size or a weight
  • You can also set the view with some content to wrap_content. Then LinearLayout will respect the size of content for this view when distributing space via weight.
  • CAN NOT make a child take all the remaning space. (Use RelativeLayout for that!)

Achieving that is easy, when the width of children is given:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal" 
    android:layout_width="fill_parent" 
    android:layout_height="fill_parent">
    <ImageView android:layout_width="50dip" android:layout_height="100dip" android:background="#cc0000"/>
    <TableLayout android:layout_width="wrap_content" android:layout_height="wrap_content">
        <TableRow>
            <ImageView android:layout_width="50dip" android:layout_height="50dip" android:background="#aaaa00"/>
            <ImageView android:layout_width="50dip" android:layout_height="50dip" android:background="#00aa00"/>
            <ImageView android:layout_width="50dip" android:layout_height="50dip" android:background="#aaaa00"/>
        </TableRow>
        <TableRow>
            <ImageView android:layout_width="50dip" android:layout_height="50dip" android:background="#00aa00"/>
            <ImageView android:layout_width="50dip" android:layout_height="50dip" android:background="#aaaa00"/>
            <ImageView android:layout_width="50dip" android:layout_height="50dip" android:background="#00aa00"/>
        </TableRow>
    </TableLayout>
    <ImageView android:layout_width="50dip" android:layout_height="100dip" android:background="#cc0000"/>
</LinearLayout>

Trying to make the table in the middle filling the remaning space fails, because the table

Example about how to use weight, width, wrap_content:

FlowLayout

There is a custom FlowLayout here https://github.com/ApmeM/android-flowlayout which allows:

  • Automatical breaking the line to wrap the content, when the space is exceeded. No default layout is able to do that.

Maven dependency:

<dependency>
    <groupId>org.apmem.tools</groupId>
    <artifactId>app</artifactId>
    <version>1.0</version>
</dependency>

Dialogs

The Dialogs are quite nice in Android. They are customizable and may have a complete custom layout.

// Create custom dialog object with layout R.layout.dialog

                final Dialog dialog = new Dialog(CustomDialog.this);
                // Include dialog.xml file
                dialog.setContentView(R.layout.dialog);
                // Set dialog title
                dialog.setTitle("Custom Dialog");
 
                // set values for custom dialog components - text, image and button
                TextView text = (TextView) dialog.findViewById(R.id.textDialog);
                text.setText("Custom dialog Android example.");
                ImageView image = (ImageView) dialog.findViewById(R.id.imageDialog);
                image.setImageResource(R.drawable.image0);
 
                dialog.show();
                 
                Button declineButton = (Button) dialog.findViewById(R.id.declineButton);
                // if decline button is clicked, close the custom dialog
                declineButton.setOnClickListener(new OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        // Close dialog
                        dialog.dismiss();
                    }
                });

However it is difficult to style the dialog completele! E.g. you can not make the DialogDivider in native DIalogs to have a custom color.

android-styled-dialogs

Allows to style the dialogs completele.

On GitHub https://github.com/inmite/android-styled-dialogs
On Maven
<dependency>
    <groupId>eu.inmite.android.lib</groupId>
    <artifactId>android-styled-dialogs</artifactId>
    <version>1.1.2</version>
    <type>apklib</type>
</dependency>

ListView AlerDialog

ListView dialogs with custom Views in the list may be very useful! They look native too so use them if possible!

To achieve that do the follwing:

1. Create a custom ListView Item
  • It should Inherit from a ViewGroup
  • It should have blockdescedants attribute set. Otherwise you wont be able to choose it in the list
  • Achtung: you may not use the methods, which make the items clickable, focuasble … Otherwise the items in the list will not be clickable.
    //cant use this for items in lists using android.content.DialogInterface.OnClickListener
    item.setClickable(true);
    item.setFocusable(true);
    item.setFocusableInTouchMode(true);

See this thread: http://stackoverflow.com/questions/5551042/onitemclicklistener-not-working-in-listview-android

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:descendantFocusability="blocksDescendants"
    android:orientation="vertical" >

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <LinearLayout
            android:id="@+id/rightCenteredContainer"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true"
            android:orientation="vertical" >

            <Button
                android:id="@+id/buttonChooseTrip"
                style="@style/Green.Button"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:clickable="true"
                android:height="@dimen/btn_trip_size"
                android:text=">"
                android:width="60dp" />
        </LinearLayout>

        <TableLayout
            android:layout_width="wrap_content"
            android:layout_height="60dp"
            android:layout_alignParentLeft="true"
            android:layout_toLeftOf="@id/rightCenteredContainer"
            android:gravity="center_vertical"
            android:padding="5dp" >

            <TableRow
                android:layout_width="wrap_content"
                android:layout_height="wrap_content">

                <TextView
                    android:id="@+id/dialogTripsDestinations"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:background="@color/transparent"
                    android:text="München (ZOB) - Augsburg (Zentrum)"
                    android:textStyle="bold" />
            </TableRow>

            <TableRow
                android:layout_width="wrap_content"
                android:layout_height="wrap_content">

                <TextView
                    android:id="@+id/dialogTripsTimes"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:background="@color/transparent"
                    android:text="16.55 - 16.10.2014"
                    android:textSize="12dp" />
            </TableRow>
        </TableLayout>
    </RelativeLayout>

</LinearLayout>
2. Create a View class which will wrap the XML Layout


import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;

public class DialogueTripItem extends LinearLayout{
	
	Context mContext;
	
	TextView dialogTripsDestinations;
	TextView dialogTripsTimes;
	Button buttonChooseTrip;
	
	public DialogueTripItem(Context context) {
		super(context);
		init(context);
	}
	public DialogueTripItem(Context context, AttributeSet attrs) {
		super(context, attrs);
		init(context);
	}
	public DialogueTripItem(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		init(context);
	}

	private void init(Context context){
		this.mContext = context;
		LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
		inflater.inflate(R.layout.dialog__select_current_trip_item, this, true);
		
		dialogTripsDestinations = (TextView) findViewById(R.id.dialogTripsDestinations);
		dialogTripsTimes = (TextView) findViewById(R.id.dialogTripsTimes);
		buttonChooseTrip = (Button) findViewById(R.id.buttonChooseTrip);
	}
	
	public void setDestinations(String dialogTripsDestinations){
		this.dialogTripsDestinations.setText(dialogTripsDestinations);
	}
	public void setDialogTripsTimes(String dialogTripsTimes){
		this.dialogTripsTimes.setText(dialogTripsTimes);
	}
	public Button getButton(){
		return buttonChooseTrip;
	}
}

3. Create the AlerDialog

ListAdapter adapter = new ListAdapter();
final DialogueTripItem item = new DialogueTripItem(activity);
adapter.content.add(item);	

AlertDialog.Builder builder = new IvuColorsAlertDialogBuilder(activity);
        builder.setAdapter(adapter, new android.content.DialogInterface.OnClickListener() {
			@Override
			public void onClick(DialogInterface dialog, int which) {
				Toast.makeText(getApplicationContext(), "Click "+which, Toast.LENGTH_LONG).show();
			}
		});
        builder.setTitle("Title");
        builder.setIcon(activity.getResources().getDrawable(R.drawable.icon_xhdpi));
        builder.setNegativeButton("Cancel", cancelListener);

Installing an Application programmatically

An APK installation may be started programmatically. The use case was to put a second (library) application into the assets folder of the main applicaiton. Install the library application on demand.

Important:

  • The applicaiton may not be started from the assets folder. It has to be copied to the external storage

private void installApk() {
		Intent intent = new Intent(Intent.ACTION_VIEW);
		
		// the name of the apk in the assests folder 
		String assetFileName = "CommonBarcode115.apk";
		
		// location where the app will be temporary copied to
		File cacheFolder = new File(Environment.getExternalStorageDirectory().getAbsolutePath()+"//Android//data//de.example.maintestapp");
		
		// the ful path to the cache file
		File fileCache = new File(cacheFolder, assetFileName);
		
		// copy the file to the cache
		try {
			// needs android.permission.WRITE_EXTERNAL_STORAGE
			fileCache.getParentFile().mkdirs();
			fileCache.createNewFile();
			
			// delete old file
			if (fileCache.exists()) {
				fileCache.delete();
			}
			
			copyAsset(assetFileName, cacheFolder);
			
		} catch (Exception e) {
			Toast.makeText(getApplicationContext(), "Ein Fehler beim kopieren",
					Toast.LENGTH_LONG);
		}

		if (!fileCache.exists()) {
			// if (!assetExists(this.getAssets(), assetFile)) {
			Toast.makeText(getApplicationContext(), "APK not found",
					Toast.LENGTH_LONG).show();
			return;
		}
		intent.setDataAndType(Uri.fromFile(fileCache),"application/vnd.android.package-archive");
		startActivity(intent);
	}
	
	private void copyAsset(String assetFileName, File copyToFolderPath) {
	    AssetManager assetManager = getAssets();
	    String[] files = null;
	    try {
	        files = assetManager.list("");
	    } catch (IOException e) {
	    	Toast.makeText(getApplicationContext(), "Failed to load assets",
					Toast.LENGTH_LONG).show();
	    }
	    for(String filename : files) {
	    	if(!filename.equals(assetFileName)){
	    		continue;
	    	}
	    	
	        InputStream in = null;
	        OutputStream out = null;
	        try {
	          in = assetManager.open(filename);
	          File outFile = new File(copyToFolderPath, filename);
	          out = new FileOutputStream(outFile);
	          copyFile(in, out);
	          in.close();
	          in = null;
	          out.flush();
	          out.close();
	          out = null;
	        } catch(IOException e) {
	            Toast.makeText(getApplicationContext(), "Failed to copy asset file",
						Toast.LENGTH_LONG).show();
	        }       
	    }
	}

	private void copyFile(InputStream in, OutputStream out) throws IOException {
		byte[] buffer = new byte[1024];
		int read;
		while ((read = in.read(buffer)) != -1) {
			out.write(buffer, 0, read);
		}
	}

	private static boolean assetExists(AssetManager assets, String name) {
		try {
			// using File to extract path / filename
			// alternatively use name.lastIndexOf("/") to extract the path
			File f = new File(name);
			String parent = f.getParent();
			if (parent == null)
				parent = "";
			String fileName = f.getName();
			// now use path to list all files
			String[] assetList = assets.list(parent);
			if (assetList != null && assetList.length > 0) {
				for (String item : assetList) {
					if (fileName.equals(item))
						return true;
				}
			}
		} catch (IOException e) {
			// Log.w(TAG, e); // enable to log errors
		}
		return false;
	}

Debugging HTTP Communication

To Capture HTTP Communication of Android, to simulate slow connection etc. use the following Tools:

In your virtual device,

  • Go to Android settings menu
  • In Wireless & Networks section, select Wi-Fi
  • Press and hold for 2 seconds WiredSSID network in the list
  • Choose Modify Network
  • Check Show advanced options
  • Select Manual for Proxy settings menu entry
  • Enter the proxy address: the Fiddler-running PC's IPAddress and Port 8888
  • Press the Save button

In Fiddler,

  • Click Tools menu > Fiddler Options > Connections
  • Tick the Allow Remote Computers to connect box
  • Restart Fiddler.
As an alternative to Genymotion

Use the Connectify, to connect your Phone with the Net via the PC with Fiddler.
Configure FIddler as described above.

Fragments

Fallpits
  • Fragments can not be nested. So use Fragments only for top level pieces when planning app's layout

Fragments can be defined in XML by using a

  • a fragment tag
  • a class attribute
              <fragment android:id="@+id/fragmentMainContent"
		        class="com.amberfog.mapslidingtest.app.FragmentMainContent"
		        android:layout_width="match_parent"
		        android:layout_height="match_parent"/>

The minimal fragment class looks as following

public class FragmentMainContent extends Fragment {
	
	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container,
			Bundle savedInstanceState) {
		return inflater.inflate(R.layout.fragment_maincontent_layout, container,false);
	}
}

where the fragment_maincontent_layout may look as whatever you like. For example:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <GridLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:columnCount="1" >

        <AnalogClock
            android:id="@+id/analogClock1"
            android:layout_column="0"
            android:layout_gravity="center"
            android:layout_row="0" />

    </GridLayout>

</LinearLayout>
  • The function, which makes Android restore the Fragment without information loss fragment.setRetainInstance(true) makes the platform find the fragment on configuration change and re-instantiate it, when the application lifecycle is restarted on configuraiton change.
    This means that the Application has to make sure, that the newly restored fragment is not overridden onCreate()
     
    
    // ACHTUNG: when using setRetainInstance(true) on a fragment to restore it's vars on configchange - check whether the fragment already exists before recreating it
    this.fragmentDetails = (Fragment1Details) fragmentManager.findFragmentByTag("fragmentTag");
    if(this.fragmentDetails==null) {
      // create the fragment here and tag it with "fragmentTag"
    }
  • The function fragment.setRetainInstance(true) only works, if the fragment is not added to the back stack
ClassUsageDetails
FragmentManager
Activity.getFragmentManager()
Used to control the Fragments
FragmentTransaction
getFragmentManager().beginTransaction()
Is able to create, update, remove fragments

Fragments may be either created in xml or programmatically.

In XML

    <fragment
        android:id="@+id/fragmentId"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">
    </fragment>

Programmatically. Programmatically fragment operations are done via FragmentManager which create FragmentTranscation
ACHTUNG: The fragment must live inside a ViewGroup R.id.fragmentContainer ist such a ViewGroup here

// get Fragment manager
FragmentManager fragmentManager = getFragmentManager();

// ACHTUNG: when using setRetainInstance(true) on a fragment to restore it's vars on configchange - check whether the fragment already exists before recreating it
this.fragmentDetails = (Fragment1Details) fragmentManager.findFragmentByTag("fragmentDetails");

if(this.fragmentDetails==null) {
    // start a transaction
    FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
    // create a fragment
    this.fragmentDetails = new Fragment1Details();
    // set the id to the fragment
    // The Fragment should not be recreated when the Application is killed
    this.fragmentDetails.setRetainInstance(true);
    // add the fragment to the view
    fragmentTransaction.add(R.id.fragmentContainer, this.fragmentDetails, "fragmentDetails");
//        //make transaction reversable by undoing the action on the fragmentmanager
//        fragmentTransaction.addToBackStack(null);
    // commit the transaction
    fragmentTransaction.commit();

    // init the value from model of fragmentDetails.
    myActivityModel.setText(myActivityModel.getText());
}else{
    Log.d(TAG,"Fragment already exists. Do not recreate it!");
}

ScrolView

To scroll the View continous prgrammatic scrolling. To scroll continously the view has to be scrolled by a little delta in a regular time period.
The period of time should be nearly 24 Frames per second, which is every 41 ms.
The distance-change in px per ms depend from the speed you wish to achieve.

0.1 - 0.2 PX per MS Slow movement
0.4 - 0.7 PX per MS Middle speed
1 PX - 1.4 PX per MS Fast speed
12PFS Seems to be not enough eince you can see the picture jerking
24PFS Seems to be ok
36PFS No difference to 24FPS

COde to test differend scroll speeds


        return new Thread(new Runnable() {
            @Override
            public void run() {

                    // scroll time
                    int scrollTimeMs = 4000;

                    // SPEED IN PX / MS, IF THE IMAGE WULD MOVE CONTIONOUSLY
                    double pxPerMsStart = 0.2;
                    double pxPerEnd = 0.8;
                    double pxPerMsChangeBy =  0.1;

                    // FPS
                    int fpsStart = 24; //per sec
                    int fpsEnd = 24;
                    int fpsChangeBy = 12;


                    // iterate pause time
                    for(double scrollspeedPxPerMs = pxPerMsStart; scrollspeedPxPerMs <=pxPerEnd; scrollspeedPxPerMs +=pxPerMsChangeBy){

                        // iterate steps
                        for(int fps=fpsStart; fps<= fpsEnd; fps+=fpsChangeBy){

                            // pauses in ms are computed from fps
                            pauseMs    = 1000/fps;

                            /*  the speed is given for the case, when the image moves contingously
                                because there is a redraw pause between movements - we have to multiply the pause in MS with the speed
                                to know to which amount the image has to be moved after every pause
                            */
                            steppx = (int) Math.round((double)pauseMs * scrollspeedPxPerMs);


                            // timestemp for next iteration
                            timestampWhenToStop = System.currentTimeMillis() + scrollTimeMs;

                            // annouce settings
                            final String message = String.format("Step by %s px, every %s ms  - FPS: %s Scroll by %s px per MS", steppx, pauseMs, fps, scrollspeedPxPerMs );
                            Log.d("stepscroll", message);

                            // jump to the top
                            ViewGroupAnimatedActivity6.this.runOnUiThread(new Runnable() {
                                public void run() {
                                    Toast.makeText(getApplicationContext(), message, Toast.LENGTH_SHORT).show();
                                    scrollView.setSmoothScrollingEnabled(false);
                                    scrollView.scrollTo(0, 0);
                                    scrollView.setSmoothScrollingEnabled(true);
                                }
                            });

                            // scroll to the bottom
                            boolean stopIteration = false;
                            while (!stopIteration){

                                ViewGroupAnimatedActivity6.this.runOnUiThread(new Runnable() {
                                    public void run() {
                                        scrollView.smoothScrollBy(0, steppx);
                                    }
                                });

                                if(timestampWhenToStop < System.currentTimeMillis()){
                                    stopIteration = true;
                                    continue;
                                }
                                try {
                                    Thread.sleep(pauseMs);
                                } catch (InterruptedException e) {
                                    e.printStackTrace();
                                }

                                if(!isRunning){
                                    return;
                                }
                            }

                        }// for fps
                    } // for pxPerEnd

            isRunning = false;
            }
        });
    }

Gradle

To execute gradle tasks - a gradle wrapper file is used. The wrapper is a shell / batch script, which downloads the gradle and so does not require gradle to be installed previously. The wrapper uses it's own gradle version The name of the file is gradlew.bat

To generate the gradle wrapper use gradle and add the wrapper :

gradle wrapper

AIDL

aidl

- all aidl have to be located in th same packages - can pass Parcels or primitives - implement aidl to be able to retrieve a common interface to communicate between different applicaitons, which are different processes

		public void onServiceConnected(ComponentName name, IBinder service) {
			IServiceReadTicketBinder serviceReadTicketBinder = IServiceReadTicketBinder.Stub.asInterface(service);
			// IServiceReadTicketBinder serviceReadTicketBinder = (IServiceReadTicketBinder)service; // not allowed when retriveing service from another application. service var is of type proxy

- add in out inout as stated here http://www.app-solut.com/blog/2011/05/using-self-defined-parcelable-objects-during-an-android-aidl-rpc-ipc-call/ - use Bundle.class to exchange data with the Service. It provides possibility to restore the stored values typesafe. Bundle is like a Hashmap which can store different types of values result.getString(“Test”)

Bindings

The bare minimum in code

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {

        final ViewDataBinding binding = DataBindingUtil.inflate(
                inflater, R.layout.fragment_poidialog, container, false);
        final View view = binding.getRoot();
        return view;
    }

The binding in xml

https://stackoverflow.com/questions/35287302/android-data-binding-how-to-pass-variable-to-include-layout

<?xml version="1.0" encoding="utf-8"?>
<layout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        >

        <include
            layout="@layout/layout_common"
            app:passedText="@{@string/app_name}" // here we pass any String 
            />

    </LinearLayout>
</layout>





<?xml version="1.0" encoding="utf-8"?>
<layout
    xmlns:android="http://schemas.android.com/apk/res/android"
    >

    <data>
        // declare fields
        <variable
            name="passedText"
            type="String"/>
    </data>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@{passedText}"/> //set field to your view.

</layout>

Style

Styles, attributes

TextAppearance

The text size in android is controlled by “TextAppearance”

Material Design provides 13 type “styles” that are applied to all the text in your app. Each of these have a design term (eg. “Body 1”) along with a corresponding type attribute that can be overridden in your app theme (eg. textAppearanceBody1). There are default “baseline” values (text size, letter spacing, capitalization, etc.) for each style.

https://material.io/blog/android-material-theme-type

Usage in your view

At the end, to express which size the text in your custom view should have instead of influencing “textSize” directly which will lead to difficulties with consistancy across app and consistancy with android native textSizes

you should set “android:textAppearance=” on your view.

and the value should be one of androids own textAppearance attributes:

  • textAppearanceHeadline1
  • textAppearanceHeadline2
  • textAppearanceHeadline3
  • textAppearanceHeadline4
  • textAppearanceBody1
  • textAppearanceBody2

The same attributes are used by android too, so your view will be consistant with android natives

In your someAppPartLayout.xml

                <!-- My title in my app view -->
                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="Title"
                    android:textAppearance="?attr/textAppearanceHeadline4" />
Changing the value of attributes in AppTheme

In your theme, maybe defined in your styles.xml you can change single android attributes.

In my theme, which is defined in this style - overriding the single attributes “textAppearance*” referencing MY styles, which can interfere and override the default values for textAppearance.

Remark: Those textAppearance* attributes are also used in android native views, like buttons see https://material.io/blog/android-material-theme-type. So overriding those in a theme - changes the behavior also for Android native elements

THose attributes are not just strings, but are introduced as typed attributes in attrs.xml with format=“reference”. format=“reference” means, that one can take a “<style></style>” element as a value. In the value-style element - the items a la android:textSize are expected (convention).

USAGE of attributes The attribute can be USED in views, by refering to the attribute in the view e.g.

android:textAppearance="?attr/textAppearanceHeadline4" /> 

In your styles.xml pointing textAppearanceHeadline1 attribute of type “reference” to my own style.

The style inherits from androids own style but I still can override some values in my inherited style.

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.MaterialComponents.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>

       
        <item name="textAppearanceHeadline1">
            @style/TextAppearance.Headline1
        </item>
Introducing own styles, to refer from overridden attributes textAppearance*

THe value of an overridden attribute textAppearance* is a style

in your styles.xml parallel to the AppTheme - introduce styles, to refer from attributes.

    <!-- here I can change attributes of the STYLES.
    by Inheriting from "TextAppearance.MaterialComponents.*" I inherit all the native properties
    but get the possibility to override them here
    -->
    <style name="TextAppearance.Headline1" parent="TextAppearance.MaterialComponents.Headline1">
        <item name="android:textSize">96sp</item>
    </style>

    <style name="TextAppearance.Headline2" parent="TextAppearance.MaterialComponents.Headline2">
        <item name="android:textSize">60sp</item>
    </style>

    <style name="TextAppearance.Headline3" parent="TextAppearance.MaterialComponents.Headline3">
        <item name="android:textSize">48sp</item>
    </style>

    <style name="TextAppearance.Headline4" parent="TextAppearance.MaterialComponents.Headline4">
        <item name="android:textSize">34sp</item>
    </style>

    <style name="TextAppearance.Headline5" parent="TextAppearance.MaterialComponents.Headline5">
        <item name="android:textSize">24sp</item>
    </style>

    <style name="TextAppearance.Headline6" parent="TextAppearance.MaterialComponents.Headline6">
        <item name="android:textSize">20sp</item>
    </style>

    <style name="TextAppearance.Body1" parent="TextAppearance.MaterialComponents.Body1">
        <item name="android:textSize">16sp</item>
    </style>

    <style name="TextAppearance.Body2" parent="TextAppearance.MaterialComponents.Body2">
        <item name="android:textSize">14sp</item>
    </style>
android.txt · Last modified: 2022/09/03 22:42 by skipidar