= Android Mobile Development =
== Resources ==
Downloads:
* [[https://developer.android.com/index.html|Android Development Resources]]
* [[https://developer.android.com/studio/index.html|Android Studio (IDE)]]
* [[https://flutter.io/get-started/codelab/|Flutter (IDE)]]
* [[https://www.visualstudio.com|Visual Studio (IDE) + Xamarin]] - Select //Community Edition//
Videos:
* [[https://www.youtube.com/watch?v=ZLNO2c7nqjw|Edureka: Android Studio Tutorial For Beginners - 1]]
* [[https://www.youtube.com/watch?v=D-iqMlLOrec|Edureka: Android Studio Tutorial For Beginners - 2]]
* [[https://www.youtube.com/watch?v=dFlPARW5IX8|Butterfield: Studio For Beginners Part 1]]
* [[https://www.youtube.com/watch?v=6ow3L39Wxmg|Butterfield: Studio For Beginners Part 2]]
* [[https://www.youtube.com/watch?v=rdGpT1pIJlw|Butterfield: Studio For Beginners Part 3]]
* [[https://www.youtube.com/watch?v=bu5Y3uZ6LLM|Butterfield: Studio For Beginners Part 4]]
How To:
* [[http://envyandroid.com/android-studio-extract-strings-resources|How to extract strings into strings.xml resource files in Android Studio]]
== App Lifecycle ==
* onCreate()
* onStart()/onRestart()
* onResume()
* onPause()
* onStop()
* onDestroy()
See: [[https://developer.android.com/guide/components/activities/activity-lifecycle.html|Android Activity Lifecycle]]
== Activities ==
Activities are equivalent to forms, and can be call directly by any other activity or app.
''findViewById()'' is a way to locate resources within Activity.
== Event Listeners ==
* onClick()
* onFocusChange()
* onLongClick()
* onKey()
* onTouch()
Example 1:
public class MainActivity extends AppCompatActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Button btnAdd = (Button) findViewById(R.id.btnAdd);
btnAdd.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// Do something, such as add to values
EditText txtVal1 = (EditText) findViewById(R.id.txtVal1);
EditText txtVal2 = (EditText) findViewById(R.id.txtVal2);
int val1 = (int) Integer.parseInt(txtVal1.getText().toString());
int val2 = (int) Integer.parseInt(txtVal2.getText().toString());
int result = val1 + val2;
final TextView lblResult = (TextView) findViewById(R.id.lblResult);
lblResult.SetText(result + ""); // append empty string to convert operation to string
Log.d("MyApp", "We clicked a button!");
Toast.makeText(this, "We clicked a button!", Toast.LENGTH_SHORT).show();
}
});
}
}
Example 2: Sharing onClickListener with multiple buttons in same Activity:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
// Resources
private TextView txt;
private Button btn;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
txt = (TextView) findViewById(R.id.txtTest);
btn = (Button) findViewById(R.id.btnTest);
btn.setOnClickListener(this);
}
@Override
public void onClick(View view) {
// do something
switch(view.getId()) {
case R.id.btnTest:
Log.d("MyApp", "We clicked a button!");
txt.SetText("We clicked a button!");
Toast.makeText(this, "YEs!!", Toast.LENGTH_SHORT).show();
break;
// case ...
}
}
}
== Intents ==
Call for an activity into action (your own or other apps), which can carry a data payload. Eg: Calling dialer from your app, or calling email client when sending an email, or calling a new activity from within your app.
To deliver an intent:
* context.startActivity()
* context.startService()
* context.sendBroadcast()
To handle an intent:
* IntentService
* BroadcastReceiver
=== Implicit Intent ===
Intents that do not define which activity will handle the request, but rather seek a registered app to handle the provided data payload.
// Call dialer app to handle phone number
Intent intentDialNumber = new Intent(Intent.ACTION_DIAL, Uri.parse("tel:4071233500"));
if (intentDialNumber.resolveActivity(getPackageManager()) != null) {
startActivity(intentDialNumber);
}
// Call contacts to handle contact selection
Intent intentSelectContact = new Intent(Intent.ACTION_DIAL, new Uri("content://contacts"));
if (intentSelectContact.resolveActivity(getPackageManager()) != null) {
startActivity(intentSelectContact);
}
// Call web browser to handle URL
Intent intentViewWebsite = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.acme.com"));
if (intentViewWebsite.resolveActivity(getPackageManager()) != null) {
startActivity(intentViewWebsite);
}
// Call a mapping app to handle geolocation (lat,long,zoom)
Intent intentShowLocation = new Intent(Intent.ACTION_VIEW, Uri.parse("geo:28.6027902,-81.4232732,14z"));
if (intentShowLocation.resolveActivity(getPackageManager()) != null) {
startActivity(intentShowLocation);
}
// Display all activities supporting Action View that can handle intent
Intent intentShowAllActivities = new Intent(Intent.ACTION_VIEW);
if (intentShowAllActivities.resolveActivity(getPackageManager()) != null) {
startActivity(intentShowAllActivities);
}
=== Explicit Intent ===
Intents that fully define the required activity that should handle the action and its data payload.
For example, in the ''MainActivity'', you can define an intent:
//...
public class MainActivity extends AppCompatActivity
{
@Override
public void onClick(View view)
{
switch(view.getId()) {
case R.id.btnShowIntent:
Intent intentDataHandler = new Intent(this, TargetActivity.class);
intentDataHandler.putExtra("Key1", "Value1"); // data payload (optional)
intentDataHandler.putExtra("Key2", "Value2"); // data payload (optional)
intentDataHandler.putExtra("com.acme.myapp.Key3", "Value3"); // use full path with key (recommended)
intentDataHandler.putExtra("Key4.TEAM_OBJ", aTeamObj); // parcelable object (eg. object of type Team)
startActivity(intentDataHandler);
break;
}
}
}
In your target activity, retrieve the intent as follows:
//...
public class TargetActivity extends AppCompatActivity
{
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(saveInstanceState);
setContentView(R.layout.activity_target);
//-----------------------------------
// Get data payload (if available)
//-----------------------------------
// Method 1
Bundle bundle = getIntent().getExtras();
String str = bundle.getString("Key1");
Toast.makeText(this, str, Toast.LENGTH_SHORT).show();
// Method 2
Intent intent = getIntent();
if (intent.hasExtra("Key2")) {
str = intent.getExtras().getString("Key2");
Toast.makeText(this, str, Toast.LENGTH_SHORT).show();
}
// Method 3
if (getIntent().hasExtra("Key3")) {
str = getIntent().getExtras().getString("Key3");
Toast.makeText(this, str, Toast.LENGTH_SHORT).show();
}
// Parcelable data
Team objTeam;
if (intent.hasExtra("Key4.TEAM_OBJ")) {
Log.d("MyApp", "Retrieving intent extras...");
objTeam = intent.getParcelableExtra("Key4.TEAM_OBJ");
Toast.makeText(this, "Team Name: "+ objTeam.name, Toast.LENGTH_SHORT).show();
}
}
}
A parcelable extra must be of a type supporting parcelable. For example:
package com.acme.myapp;
import android.os.Parcel;
import android.os.Parcelable;
public class Team implements Parcelable
{
public long id = 0;
public String name = "";
public String number = "";
public int ranking = 0;
// Default constructor
Team()
{
}
// In constructor, read variables from Parcel.
// Important: Read them in the same sequence in which they were written in Parcel.
public Team(Parcel in) {
id = in.readLong();
name = in.readString();
number = in.readString();
ranking = in.readInt();
}
@Override
public int describeContents() {
return 0;
}
// Write member variables in Parcel.
// Write in any order. Not required to write all members in Parcel.
@Override
public void writeToParcel(Parcel dest, int flags) {
// Write data in any order
dest.writeLong(id);
dest.writeString(name);
dest.writeString(number);
dest.writeInt(ranking);
}
// De-serialize the object
public static final Parcelable.Creator CREATOR = new Parcelable.Creator(){
public Team createFromParcel(Parcel in) {
return new Team(in);
}
public Team[] newArray(int size) {
return new Team[size];
}
};
}
=== Intent Examples ===
For example, in the ''MainActivity'', you can define an intent:
//...
public class MainActivity extends AppCompatActivity implements View.OnClickListener
{
// Resources
private TextView txtHello;
private Button btnDialNumber;
private Button btnViewWebsite;
//...
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
txtHello = (TextView) findViewById(R.id.txtHello);
btnDialNumber = (Button) findViewById(R.id.btnDialNumber);
btnDialNumber.setOnClickListener(this);
btnViewWebsite= (Button) findViewById(R.id.btnViewWebsite);
btnViewWebsite.setOnClickListener(this);
//...
}
@Override
public void onClick(View view)
{
switch(view.getId()) {
case R.id.btnDialNumber:
Intent intentDialNumber = new Intent(Intent.ACTION_DIAL, Uri.parse("tel:4071233500"));
if (intentDialNumber.resolveActivity(getPackageManager()) != null) {
startActivity(intentDialNumber);
}
break;
case R.id.btnViewWebsite:
Intent intentViewWebsite = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.acme.com"));
if (intentViewWebsite.resolveActivity(getPackageManager()) != null) {
startActivity(intentViewWebsite);
}
break;
case R.id.btnShowLocation:
Intent intentShowLocation = new Intent(Intent.ACTION_VIEW,
Uri.parse("geo:28.6027902,-81.4232732,14z" // lat,long,zoom
));
if (intentShowLocation.resolveActivity(getPackageManager()) != null) {
startActivity(intentShowLocation);
}
break;
case R.id.btnShowAllActivities:
// Display all activities supporting Action View that can handle intent
Intent intentShowAllActivities = new Intent(Intent.ACTION_VIEW);
if (intentShowAllActivities.resolveActivity(getPackageManager()) != null) {
startActivity(intentShowAllActivities);
}
break;
case R.id.btnShowToast:
Intent intentShowToast = new Intent(MainActivity.this,
SampleActivity.class // target activity
);
if (intentShowToast.resolveActivity(getPackageManager()) != null) {
startActivity(intentShowToast);
}
break;
case R.id.btnShowSampleActivity:
Intent intentShowSampleActivity = new Intent("com.acme.myapp.SampleActivity");
if (intentShowSampleActivity.resolveActivity(getPackageManager()) != null) {
startActivity(intentShowSampleActivity);
}
break;
}
}
}
''activity_main.xml'':
In the ''manifest.xml'', you can add intent filters:
== Debugging with logcat ==
Log.d("MyAppTag", "A debug message"); // standard debug message
Log.e("MyAppTag", "A debug message"); // error message
Log.w("MyAppTag", "A debug message"); // warning message
Log.i("MyAppTag", "A debug message"); // info message
== ListView ==
=== Resources ===
Under resources (''app'' > ''res'' > ''values''), add string arrays in ''strings.xml''. These will be data bound to a ''ListView'' control:
MyAppTableChairStool$1.00$1.50$1.25RedBlueGreen
=== Data Binding ===
In the default ''onCreate()'' method, add references to the ''ListView'':
package com.acme.myapp;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// ADD ListView here...
}
}
Add a ''ListView'' with a custom item view using an ''ItemAdapter''. You can choose the type of item view:
* [[swdev:android:start#textview_layout_for_item|TextView]]
* [[swdev:android:start#relativelayout_constraintlayout_layout_for_item|RelativeLayout]]
* [[swdev:android:start#relativelayout_constraintlayout_layout_for_item|ConstraintLayout]]
=== TextView layout for item ===
To use a custom layout based on ''TextView'' layout, create ListViewDetail layout:
* Go to project tree, select ''app'' > ''res'' > ''layout''. Right-click and select ''New'' > ''Layout resource file''.
* File name: ''listview_detail''
* Root element: ''TextView''
Activity code:
public class MainActivity extends AppCompatActivity
{
// Properties
ListView lstProducts;
String[] arrProducts;
String[] arrPrices;
String[] arrColors;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Resources res = getResources();
lstProducts = (ListView) findViewById(R.id.lstProducts);
arrProducts = res.getStringArray(R.array.arrProducts);
arrPrices = res.getStringArray(R.array.arrPrices);
arrColors = res.getStringArray(R.array.arrColors);
// Example: Using layout resource based on TextView
lstProducts.setAdapter(new ArrayAdapter(
this, // context
R.layout.listview_detail, // detail view layout
arrProducts // data
));
}
}
=== RelativeLayout/ConstraintLayout layout for item ===
You will need the following:
* [[swdev:android:start#item-detail_activity_to_be_used_by_itemadapter|Item-Detail activity]]
* [[swdev:android:start#itemadapter|ItemAdapter class]]
==== Item-Detail Activity (to be used by ItemAdapter) ====
To use a custom layout based on ''RelativeLayout'' or ''ConstraintLayout'', create ListViewDetail layout:
* Go to project tree, select ''app'' > ''res'' > ''layout''. Right-click and select ''New'' > ''Layout resource file''.
* File name: ''listview_detail''
* Root element: ''RelativeLayout'' or ''ConstraintLayout''
* Edit detail form by adding controls (TextView, ImageView, etc.) that can be associated with the different attributes (fields) for the item object represented.
Activity code:
import com.acme.myapp.ItemAdapter;
public class MainActivity extends AppCompatActivity
{
ListView lstProducts;
String[] arrProducts;
String[] arrPrices;
String[] arrColors;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Resources res = getResources();
lstProducts = (ListView) findViewById(R.id.lstProducts);
arrProducts = res.getStringArray(R.array.arrProducts);
arrPrices = res.getStringArray(R.array.arrPrices);
arrColors = res.getStringArray(R.array.arrColors);
// Example: Using custom ItemAdapter with custom layout
ItemAdapter itemAdapter = new ItemAdapter(
this, // context
arrProducts, arrPrices, arrColors // data
);
lstProducts.setAdapter(itemAdapter);
}
}
==== ItemAdapter ====
Create class ItemAdapter
* Go to project tree, select ''app'' > ''java'' > //''''//. Right-click and select ''New'' > ''Java Class''.
* Name: ''ItemAdapter''
* Kind: ''Class''
* Superclass: ''android.widget.BaseAdapter''
package com.acme.myapp;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
public class ItemAdapter extends BaseAdapter
{
// Properties
LayoutInflater mInflater;
String[] arrProducts;
String[] arrPrices;
String[] arrColors;
//
// Constructor: Create custom contructor so properties get assigned right away
//
public ItemAdapter(Context cx, String[] prod, String[] prices, String[] colors)
{
arrProducts = prod;
arrPrices = prices;
arrColors = colors;
mInflater = (LayoutInflater) cx.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
@Override
public int getCount()
{
return arrProducts.length;
}
@Override
public Object getItem(int idx)
{
return arrProducts[idx];
}
@Override
public long getItemId(int idx)
{
return idx;
}
@Override
public View getView(int idx, View view, ViewGroup viewGroup)
{
// Use layout inflater. Use listview_detail.xml layout
View vw = mInflater.inflate(R.layout.listview_detail, null);
TextView txtProduct = (TextView) vw.findViewById(R.id.txtProduct);
TextView txtPrice = (TextView) vw.findViewById(R.id.txtPrice);
TextView txtColor = (TextView) vw.findViewById(R.id.txtColor);
String strProduct = arrProducts[idx];
String strPrice = arrPrices[idx];
String strColor = arrColors[idx];
txtProduct.setText(strProduct);
txtPrice.setText(strPrice);
txtColor.setText(strColor);
return vw;
}
}
== ImageView ==
* Copy images to ''app'' > ''res'' > ''drawable'' folder in project tree.
* Create an additional activity to display list item detail (including image)
* Add controls to display item properties in activity canvas.
* ''TextView'' control for item name and description.
* ''ImageView'' control for item image.
Add an ''ItemClickListener'' for the ''ListView'' in ''MainActivity'' activity. This will launch the secondary activity with the ''ListView'' item details:
protected void onCreate(Bundle savedInstanceState) {
// ...
lstTeams.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView> parent, View view, int position, long id) {
Log.d("RoboticsLeague", "Loading rubric for Team " + (position+1));
Toast.makeText(getApplicationContext(), "Loading rubric for Team " + (position+1), Toast.LENGTH_SHORT).show();
Intent showRubricActivity = new Intent(getApplicationContext(), RobotRubricActivity.class);
showRubricActivity.putExtra("com.voirtech.roboticsleague.TEAM_ID", (position+1)+"");
startActivity(showRubricActivity);
}
});
}
On the secondary activity, process the intent:
package com.acme.roboticsleague;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;
public class RobotRubricActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_robot_rubric);
Intent intent = getIntent();
String strTeamId = intent.getExtras().getString("com.acme.roboticsleague.TEAM_ID");
TextView lblTeamId = (TextView) findViewById(R.id.lblTeamId);
lblTeamId.setText("Team " + strTeamId);
Button btnSaveRubric = (Button) findViewById(R.id.btnSaveRubric);
btnSaveRubric.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// Do something, such as add to values
SeekBar barDurability = (SeekBar) findViewById(R.id.barDurability);
SeekBar barMechanicalEfficiency = (SeekBar) findViewById(R.id.barMechanicalEfficiency);
SeekBar barMechanization = (SeekBar) findViewById(R.id.barMechanization);
String valDurability = (String) (barDurability.getProgress() + "");
String valMechanicalEfficiency = (String) (barMechanicalEfficiency.getProgress() + "");
String valMechanization = (String) (barMechanization.getProgress() + "");
//final TextView lblResult = (TextView) findViewById(R.id.lblResult);
//lblResult.SetText(result + ""); // append empty string to convert operation to string
String msg = String.format("Saved evaluation at: %s, %s, %s", valDurability, valMechanicalEfficiency, valMechanization);
Log.d("RoboticsLeague", msg);
Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_SHORT).show();
}
});
}
}
==== Secondary Activity ====
The secondary activity with item details would look like:
== Database Connection ==
* [[swdev:android:Database MySQL]]
* [[swdev:android:Database SQLite]]