Programming for Android
- more than basics
Tomáš Kypta
@TomasKypta
Agenda
• Material design
• UX design patterns
• Dialogs
• Notifications
• Broadcast receiver
• Service
Agenda
• Fragment
• Process & memory
• Background processing
• Loader
• Software design patterns
Material Design
Android
Design
Evolution
long time ago …
Android Design Evolution
Holo
Design
Material
Design
Material Design
Material Design
• tangible surfaces and edges
• shadows
• elevation
• motion
Material Design
• themes since API level 21
• @android:style/Theme.Material (dark version)
• @android:style/Theme.Material.Light (light version)
• @android:style/Theme.Material.Light.DarkActionBar
• …
Material Design
• compatibility themes
• @style/Theme.AppCompat (dark version)
• @style/Theme.AppCompat.Light (light version)
• @style/Theme.AppCompat.Light.DarkActionBar
• …
The Color Palette
• three color hues from primary palette
• one accent color from the secondary palette
The Color Palette
• http://www.google.com/
design/spec/style/color.html
The Color Palette
The Color Palette
The Color
Palette
Toolbar
Toolbar
• generalization of action bar
• main Android navigation element
Toolbar
• ActionBarActivity
compile 'com.android.support:appcompat-v7:22.0.0'
• inherit styles from Theme.AppCompat
• for inflating views for action bar use
getSupportActionBar().getThemedContext()
Toolbar
Toolbar
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar)
findViewById(R.id.my_toolbar);
setSupportActionBar(toolbar);
}
Toolbar
• standalone mode
• setSupportActionBar(toolbar);
CardView
CardView
• showing information inside cards with consistent
look
compile 'com.android.support:cardview-v7:22.0.0'
CardView
CardView
…
Elevation
Elevation
Elevation
android:elevation=“4dp”
view.setElevation(elevation);
Transition
Transition Animation
• since API level 21
-
@android:transition/explode
-
@android:transition/explode
Transition
public class MyActivity1 extends ActionBarActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
// enable transitions before content is added
getWindow().requestFeature(
Window.FEATURE_CONTENT_TRANSITIONS);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my_1);
…
}
}
Transition
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
getWindow().setExitTransition(new Explode());
Intent intent = new Intent(MyActivity1.this,
MyActivity2.class);
startActivity(intent,
ActivityOptions
.makeSceneTransitionAnimation(MyActivity1.this)
.toBundle());
}
});
Shared Element Transition
• enable window content transitions
• transition for shared element
• define shared element with android:transitionName
• in both layouts
• ActivityOptions.makeSceneTransitionAnimation()
Shared
Element
Transition
Ripple
Ripple
• RippleDrawable
• set as view background
Ripple
Ripple
Dynamic color
State List Animator
State List Animator
• materials raise up to meet your finger
• android:stateListAnimator="@anim/raise"
State List
Animator
… and ripple
State List Animator
-
-
UX Design Patterns
Pull-to-Refresh
Pull-to-Refresh
Navigation Drawer
• panel that transitions from the left edge of the
screen
• displays the app’s main navigation options
Multi-pane Layouts
Multi-pane Layouts
• many different screen sizes and types of devices
• provide balanced and aesthetically pleasing layout
Multi-pane Layouts
• do not forget different orientations
• various strategies
Multi-pane Layouts
Swipe Views
Swipe Views
Swipe Views
• allow efficient navigation between items
• swiping between detail views
• swiping between tabs
Swipe-to-dismiss
Swipe-to-dismiss
• dismiss list item by swiping left or right
Swipe-to-dismiss with Undo
Dialogs
Dialogs
• via DialogFragment
• styling problems
Dialogs
Dialogs
• android-styled-dialogs
• https://github.com/avast/android-styled-dialogs
Dialogs
SimpleDialogFragment.createBuilder(c,
getSupportFragmentManager())
.setTitle(“Backport of material dialogs")
.setMessage(“Lorem ipsum dolor sit amet…”)
.setPositiveButtonText("OK")
.setNegativeButtonText("Cancel")
.setNeutralButtonText("Help")
.setRequestCode(REQUEST_SIMPLE_DIALOG)
.show();
Dialogs
•implement listener in activity/fragment to receive
callback
•ISimpleDialogListener
•IPositiveButtonDialogListener
•INegativeButtonDialogListener
•INeutralButtonDialogListener
Dialogs
@Override
public void onPositiveButtonClicked(int requestCode) {
if (requestCode == REQUEST_SIMPLE_DIALOG) {
Toast.makeText(c,
"Positive button clicked",
Toast.LENGTH_SHORT)
.show();
}
}
Notification
Notification
• a message that can be displayed to the user
outside your normal UI
• displayed in notification area
Notification
• user can open notification drawer to see the details
• app can define UI and click action
• NotificationCompat.Builder
Notification
NotificationCompat.Builder mBuilder =
new NotificationCompat.Builder(this)
.setSmallIcon(R.drawable.icon)
.setContentTitle("My notification")
.setContentText("Hello World!");
Notification
Intent resultIntent = new Intent(this, MyActivity1.class);
TaskStackBuilder stackBuilder =
TaskStackBuilder.create(this);
stackBuilder.addParentStack(MyActivity1.class);
stackBuilder.addNextIntent(resultIntent);
Notification
PendingIntent resultPendingIntent =
stackBuilder.getPendingIntent(
0,
PendingIntent.FLAG_UPDATE_CURRENT
);
mBuilder.setContentIntent(resultPendingIntent);
NotificationManager mNotificationManager =
(NotificationManager) getSystemService(
Context.NOTIFICATION_SERVICE);
mNotificationManager.notify(MY_ID, mBuilder.build());
Notification
• update notification by using the same ID
Broadcast Receiver
Broadcast Receiver
public class MyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// do something
}
}
Broadcast Receiver
• don’t do any threading in onReceive()
• start a service instead and do the threading there
Dynamic Registration
context.registerReceiver(mReceiver,
new IntentFilter(Intent.ACTION_SEND));
context.unregisterReceiver(mReceiver);
Service
Service
• component for background processing
• android.app.Service
• two forms
• started service
• bound bound
Service
• by default runs on the main thread!
Started Service
• to perform some operation without returning result
to the caller
• start by calling
context.startService(Intent)
• only one instance of the service is created
Started Service
@Override
public int onStartCommand(Intent intent, int flags, int
startId) {
if (ACTION_DO_SOMETHING.equals(intent.getAction())) {
// do something
}
return super.onStartCommand(intent, flags, startId);
}
Started Service
• service has to be stopped after processing the request
• otherwise it lives indefinitely
• stopSelf()
• stopSelf(int)
• stopService(Intent)
Bound Service
• for interacting with other components
• to expose functionality to other apps
• client calls bindService()
• cannot be called from broadcast receiver
Bound Service
• implement onBind()
• return null if you don’t want to allow binding
Bound Service
• clients call unbindService()
• when all clients are unbound, the system destroys
the service
• no need to stop service explicitly
Service
• Started and Bound approaches can be mixed
Service Lifecycle
• service lifetimes:
• entire lifetime
• active lifetime
• start in onStartCommand() or onBind()
Service
Lifecycle
Foreground Service
• something user is actively aware of
• must provide an ongoing notification
• cannot be dismissed
• startForeground()
• stopForeground()
Intent Service
Intent Service
• service for processing on background threads
• for processing independent on UI
• android.app.IntentService
Intent Service
public class MyIntentService extends IntentService {
public MyIntentService() {
super("MyIntentService");
}
@Override
protected void onHandleIntent(Intent intent) {
// run some code on background
}
}
Fragment
Fragment
• represents a behavior or a portion of user interface
in an activity
• multiple fragments can be combined in a single
activity
Fragment
Activity & Fragment
• add in the layout
Activity & Fragment
• add programmatically
FragmentManager fragmentManager = getFragmentManager()
FragmentTransaction transaction =
fragmentManager.beginTransaction();
ExampleFragment fragment = new ExampleFragment();
transaction.add(R.id.fragment_container, fragment);
transaction.commit();
Fragment Back Stack
• fragments can be kept in a stack
FragmentManager fragmentManager = getFragmentManager()
FragmentTransaction transaction =
fragmentManager.beginTransaction();
ExampleFragment fragment = new ExampleFragment();
transaction.add(R.id.fragment_container, fragment);
transaction.addToBackStack(null);
transaction.commit();
Fragment Back Stacks
A A
B
A
B
C
A
B
back
Fragment Lifecycle
Fragment Lifecycle
• a bit more complicated than activity lifecycle
Fragment
Lifecycle
Fragment
Lifecycle
Activity &
Fragment
Lifecycle
Activity &
Fragment
Lifecycle
Fragment Lifecycle
Callbacks
onAttach()
• fragments associated with the activity
onCreateView()
• create fragment’s view hierarchy here
onActivityCreated()
• activity’s onCreate() method has returned
Fragment Lifecycle
Callbacks
onDestroyView()
• view hierarchy is being removed
onDetach()
• fragment is being disassociated from the activity
Fragment without a UI
• aka worker fragment
transaction.add(workFragment, “work”);
Process & Memory
Process
• application starts with one Linux process with
single thread
• system can shut down a process when memory is
low
Process
• foreground process
• visible process
• service process
• background process
• empty process
Memory State
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
if (level >= TRIM_MEMORY_COMPLETE) {
// they will kill us soon!
}
if (level >= TRIM_MEMORY_UI_HIDDEN) {
// release all UI related memory
}
// more levels
}
Memory State
• retrieve current trim level at any time
• ActivityManager.getMyMemoryState(RunningAppProcessInfo)
Background
Processing
Threads
• main thread = UI thread
• never block the UI thread!!!
• use worker threads for time consuming operations
• networking, db, filesystem, …
• UI toolkit is not thread safe
• never manipulate UI from a worker thread!!!
Threads
• complications
• activities are restarted
• memory leaks
• crashes
Background Processing
• Thread
• AsyncTask
• IntentService
• Loader
• ThreadPoolExecutor
• AbstractThreadedSyncAdapter
Background Processing
Some people, when confronted with a
problem, think, "I know, I'll use threads,"
and then two they hav erpoblesms.
HandlerThread
HandlerThread
• holds a queue of tasks
• other threads can push tasks to it
• the thread processes its queue, one task after
another
• when the queue is empty, it blocks until something
appears
Looper and Handler
• Looper
• class that runs a message loop for a thread
• Handler
• provides interaction with the message loop
Looper and Handler
• sendMessage(Message)
• Message object, retrieve with
Message.obtain()
• post(Runnable)
• delayed versions, at time versions
Looper and Handler
• UI thread has Looper
• you can create easily another Handler
• Handler is bound to the Looper of the current
thread
• or you can explicitly provide different Looper
Loader
Loader
• asynchronous loading of data
• bound to activity or fragment
• monitor source of data, deliver changes
• reconnect after config change, don’t need to
requery
• managed by LoaderManager
Loader
• AsyncTaskLoader
• CursorLoader
Loader
• you have to implement
LoaderManager.LoaderCallbacks
Loader
@Override
public Loader onCreateLoader(int id, Bundle args) {
// instantiate a new loader
return null;
}
@Override
public void onLoadFinished(Loader loader, D data) {
// called when loader finished its loading
}
@Override
public void onLoaderReset(Loader loader) {
// called when loader is being reset
}
Loader
@Override
public Loader onCreateLoader(int id, Bundle args) {
return new CursorLoader(getActivity(), mUri,
mProjection, "value > ?",
new String[]{String.valueOf(5)}, "value ASC”);
}
Loader
@Override
public void onLoadFinished(Loader loader, Cursor
cursor) {
mAdapter.swapCursor(cursor);
}
@Override
public void onLoaderReset(Loader loader) {
mAdapter.swapCursor(null);
}
Loader
// prepare the loader
// either re-connect with an existing one,
// or start a new one.
getLoaderManager().initLoader(0, null, this);
Loader
// restart the loader to do a new query
getLoaderManager().restartLoader(0, null, this);
Software Design
Patterns
Component Creation
• don’t override constructors
• you don’t control component creation
• use lifecycle callbacks
Simple Dependency
Injection
• via Context
Object context.getSystemService(String)
Simple Dependency
Injection
public class MyApplication extends Application {
private MyManager mMyManager;
@Override
public Object getSystemService(String name) {
if (MyManager.class.getName().equals(name)) {
if (mMyManager == null) {
mMyManager = new MyManager();
}
return mMyManager;
}
return super.getSystemService(name);
}
}
Simple Dependency
Injection
MyManager myManager = (MyManager)
context.getSystemService(MyManager.class.getName());
Simple Dependency
Injection
• can’t be used in libraries
• you usualy don’t control the application object
View Holder
static class ViewHolder {
TextView txtName;
TextView txtDescription;
public ViewHolder(View view) {
txtName = (TextView) view.findViewById(R.id.txt_name);
txtDesc = (TextView) view.findViewById(R.id.txt_desc);
}
}
view.setTag(holder);
ViewHolder holder = (ViewHolder) view.getTag();
Cross Component
Communication
Cross Component
Communication
• broadcast
• local broadcast
• event bus
Local Broadcast
Local Broadcast
LocalBroadcastManager lbManager =
LocalBroadcastManager.getInstance(context);
lbManager.registerReceiver(mReceiver, intentFilter);
lbManager.unregisterReceiver(mReceiver);
lbManager.sendBroadcast(intent);
lbManager.sendBroadcastSync(intent);
Event Bus
Event Bus
• no direct support
• library or custom implementation
Event Bus
• Otto (http://square.github.io/otto)
Bus bus = new Bus();
bus.register(this);
bus.unregister(this);
@Subscribe
public void wasLoggedOut(LogoutEvent event) {
// do some logout action
}
Event Bus
bus.post(new LogoutEvent(LogoutEvent.LogoutType.MANUAL));
@Produce
public LogoutEvent produceLogoutEvent() {
return new LogoutEvent(LogoutEvent.LogoutType.MANUAL);
}
Questions?