==== Linux environment ====
How to set up a headless build environment.
|OS|Ubuntu 20.04|
|Android SDK|Android 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
# /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
./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]]
ACHTUNG:
- Nutze diesen Namespace, statt den der eigenen App: xmlns:app="http://schemas.android.com/apk/res-auto"
==== Fading Background ====
Is described here: http://nathanael.hevenet.com/android-dev-fading-background-animation/
==== ListView ====
== Select Item ==
listView.setItemChecked(1, true);
== Iterate Items==
ContentListAdapter contentListAdapter = (ContentListAdapter) listView.getAdapter();
for(int i=0; i
==== 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.
{{http://i520.photobucket.com/albums/w327/schajtan/2013-08-20_11-09-46_zps4723ec3c.png}}
==== Tools ====
The tool monitor.bat can be used as a Standalone logger! Details are [[http://developer.android.com/tools/debugging/ddms.html#running|here]]
{{http://i520.photobucket.com/albums/w327/schajtan/2013-09-02_11-00-07_zps3bb644a0.png}}
=== 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 ===
https://code.google.com/p/android-apktool/downloads/detail?name=apktool-install-windows-2.2_r01-3.tar.bz2
apktool d HelloWorld.apk ./HelloWorld
=== 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
barcode-lib-commons-dataprojectBarcodeLibCommonsDataProject1.0.0apklib
The dependency
de.ivu.fare.apps-13.2.PIROLbarcode-lib-commons-dataproject1.0.0apklib
==Package type==
The apk is an App.
barcodelib21.0.0apkBarcode-Lib2
The apklib - is the android lib project
barcodelib21.0.0apklibBarcode-Lib2
==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 **false**
com.jayway.maven.plugins.android.generation2android-maven-plugin3.2.0true82.3.3_API-10true${project.build.directory}/filtered-assets${project.build.directory}/filtered-manifest/AndroidManifest.xmlfalse${build.verbosity}${project.build.directory}/${project.artifactId}-${build.version.name}.apk${project.build.directory}/${project.artifactId}-${build.version.name}-aligned.apkzipalignpackagezipalign
==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|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) | [[https://support.google.com/googleplay/android-developer/answer/112622?hl=en&ref_topic=15867|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 ====
- Create a new Test Project, as [http://developer.android.com/tools/testing/testing_android.html|stated here]]
== android.test.ActivityInstrumentationTestCase2 ==
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 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 ===
https://devops.stackexchange.com/questions/3965/configuration-of-aws-codepipeline-for-android-ci-cd
==== 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)//
Use own attribute
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
=== 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 \\{{http://i520.photobucket.com/albums/w327/schajtan/2014-04-05_11-31-28_zpse32d1857.png?600}}
How to use GridLayout is described in this video:
{{youtube>tsOr0QhJZaE?medium}}
=== 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: \\
{{http://i520.photobucket.com/albums/w327/schajtan/2014-04-06_09-53-05_zps41dc325e.png?300}}
Trying to make the table in the middle filling the remaning space fails, because the table \\
{{http://i520.photobucket.com/albums/w327/schajtan/2014-04-06_09-24-52_zps57daa4b2.png?300}}
Example about how to use weight, width, wrap_content:
{{http://i520.photobucket.com/albums/w327/schajtan/2014-04-06_09-58-29_zps8e7b5d64.png?300}}
{{http://i520.photobucket.com/albums/w327/schajtan/2014-04-06_09-59-40_zpsd6056e19.png?300}}
=== 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:
org.apmem.toolsapp1.0
{{http://i520.photobucket.com/albums/w327/schajtan/2014-04-08_10-14-18_zpsbca7faaa.png}}
{{http://i520.photobucket.com/albums/w327/schajtan/2014-04-08_10-14-27_zpsab68927a.png}}
==== 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 |eu.inmite.android.libandroid-styled-dialogs1.1.2apklib |
{{https://github.com/inmite/android-styled-dialogs/raw/master/graphics/screenshot-small.png}}
== 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
== 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:
* Genymotion Simulator - http://www.genymotion.com/
* Fiddler - http://www.telerik.com/fiddler
* Connectify as alternative to Genymotion - http://www.connectify.me/
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
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:
* 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
^Class^Usage^Details^
|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
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 | {{http://i520.photobucket.com/albums/w327/schajtan/12fps_zpstu39zc1b.gif?400}} |
|24PFS| Seems to be ok | {{http://i520.photobucket.com/albums/w327/schajtan/24fps_zpszjodthm9.gif?400}} |
|36PFS| No difference to 24FPS | {{http://i520.photobucket.com/albums/w327/schajtan/36fps_zpsokht1ouu.gif?400}} |
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
// declare fields
//set field to your view.
==== Style ====
=== Styles, attributes ===
{{https://s3.eu-central-1.amazonaws.com/alf-digital-wiki-pics/sharex/0A2xTQpLXn.png}}
=== 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
{{https://s3.eu-central-1.amazonaws.com/alf-digital-wiki-pics/sharex/9SsVsrtpPT.png}}
== 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
== 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 "" 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.