In the fast development of the Susi App, somehow developers missed out some memory leaks in the app. It is a very common mistake that developers do. Most new android developers don’t know much about Memory leaks and how to fix them. Memory leaks makes the app slower and causes crashes due to OutOfMemoryException. To make the susi app more efficient, it is advised to look out for these memory leaks and fix them. This post will focus on teaching developers who’ll be contributing in Susi android App or any other android app about the memory leaks, detecting them and fixing them.
What is a memory leak?
Android system manages memory allocation to run the apps efficiently. When memory runs short, it triggers Garbage Collector (GC) which cleans up the objects which are no longer useful, clearing up memory for other useful objects. But, suppose a case when a non-useful object is referenced from a useful object. In that case Garbage Collector would mark the non-useful object as useful and thus won’t be able to remove it, causing a Memory Leak.
Now, when a memory leak occurs, the app demands for memory from the android system but the android system can only give a certain amount of memory and after that point it will refuse to give more memory and thus causing a OutOfMemoryException and crashing the app. Even if sometime due to memory leaks, the app doesn’t crash but it surely will slow down and skip frames.
Now, few other questions arises, like “How to detect these leaks?” , “What causes these leaks ?” and “How can we fix these?” Let’s cover these one by one.
Detecting Memory Leaks
You can detect memory leaks in android app in two ways :
Using Android Studio
Using Leak Canary
In this post I’ll be describing the way to use Leak Canary to detect Memory Leaks. If you want to know about the way to use android studio to detect leaks, check out this link .
Using Leak Canary for Memory Leak detection :
Leak Canary is a very handy tool when it comes to detecting memory leaks. It shows the memory leak in an another app in the mobile itself. Just add these lines under the dependencies in build.gradle file.
if (LeakCanary.isInAnalyzerProcess(this)) {
// This process is dedicated to LeakCanary for heap analysis.
// You should not init your app in this process.
return;
} LeakCanary.install(this);
// Normal app init code…
You are good to go. Now just run the app and if there is a memory leak you will get something like this. It dumps the memory in .hprof file and displays it in another app.
Causes of Memory Leaks
There are many causes of memory leaks. I will list a few top of my head but there can be more.
Static Activities and Views : Defining a static variable inside the class definition of the Activity and then setting it to the running instance of that Activity. If this reference is not cleared before the Activity’s lifecycle completes, the Activity will be leaked.
Listeners : When you register a listener, it is advised to unregister it in onDestroy() method to prevent memory leaks. It is not that prominent but may cause memory leaks.
Inner Classes : If you create an instance of Inner Class and maintain a static reference to it, there is a chance of memory leak.
Anonymous Classes : A leak can occur if you declare and instantiate an AsyncTask anonymously inside your Activity. If it continues to perform background work after the Activity has been destroyed, the reference to the Activity will persist and it won’t be garbage collected until after the background task completes.
Handlers and Threads : The very same principle applies to background tasks declared anonymously by a Runnable object and queued up for execution by a Handler object.
Preventing and Fixing Memory Leaks
So, now you know what are the causes of these memory leaks. You just have to be a little more careful while implementing these. Here are some more tips to prevent or fix memory leaks :
Be extra careful when dealing with Inner classes and Anonymous classes. Make them static wherever possible. Use a static inner class with a WeakReference to the outer class if that helps.
Be very careful with a static variable in your activity class because it can reference your activity and cause leak. Be sure to remove the reference in onDestroy().
Unregister all listeners in onDestroy() method.
Always terminate worker threads you initiated on Activity onDestroy().
Make sure that your allocated resources are all collected as expected. Do not always rely on Garbage Collector.
Try using the context-application instead of a context-activity.
Conclusion
So, now if you want to contribute in Susi Android App and implement a feature in it, you can just check if there is a memory leak due to your implementation and fix it for better performance of the app. Also, if you find any other memory leak in the app, do report it on the issue tracker, fix it and make the Susi Android App more efficient.
The Open Event Android App generator runs on a DigitalOcean. The deployment runs on a USD 10 box, that has 1 GB of RAM, but for testing I often use a USD 5 box, that has only 512mb of RAM.
When trying to build an android app using gradle and Java 8, there could be an issue where you run out of RAM (especially if it’s 512 only).
What we can do to remedy this problem is creating a swapfile. On an SSD based system, Swap spaces work almost as fast as RAM, because SSDs have very high R/W speeds.
The steps to create a swap file and allocating it as swap are
sudo fallocate -l 1G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
We can verify using
sudo swapon --show
NAME TYPE SIZE USED PRIO
/swapfile file 1024M 0B -1
And now if we see RAM usage using free -h , we’ll see
total used free shared buff/cache available
Mem: 488M 37M 96M 652K 354M 425M
Swap: 1.0G 0B 1.0G
Do not use this as a permanent measure for any SSD based filesystem. It can corrupt your SSD if used as swap for long. We use this only for short periods of time to help us build android apks on low ram systems.
The Open Event Android App, downloads data from the API (about events, sessions speakers etc), and saves them locally in an SQLite database, so that the app can work even without internet connection.
Since there are multiple entities like Sessions, Speakers, Events etc, and each Session has ids of speakers, and id of it’s venue etc, we often need to use JOIN queries to join data from two tables.
Android has some really nice SQLite helper classes and methods. And the ones I like the most are the SQLiteDatabase.query, SQLiteDatabase.update, SQLiteDatabase.insert ones, because they take away quite a bit of pain for typing out SQL commands by hand.
But unfortunately, if you have to use a JOIN, then usually you have to go and use the SQLiteDatabase.rawQuery method and end up having to type your commands by hand.
But but but, if the two tables you are joining do not have any common column names (actually it is good design to have them so – by having all column names prefixed by tablename_ maybe), then you can hack the usual SQLiteDatabase.query() method to get a JOINed query.
Now ideally, to get the Session where speaker_id was 1, a nice looking SQL query should be like this –
StringrawQuery="SELECT * FROM "+SpeakerTable.TABLE_NAME+" INNER JOIN "+SessionTable.TABLE_NAME+" ON "+SessionTable.EXP_ID+" = "+SpeakerTable.ID+" WHERE "+SessionTable.ID+" = "+id;Cursorc=db.rawQuery(rawQuery,null);
But of course, because of SQLite’s backward compatible support of the primitive way of querying, we turn that command into
Now this we can write by hacking the terminology used by the #query() method –
Cursorc=db.query(SessionTable.TABLE_NAME+" , "+SpeakerTable.TABLE_NAME,Utils.concat(SessionTable.PROJECTION,SpeakerTable.PROJECTION),SessionTable.EXP_ID+" = "+SpeakerTable.ID+" AND "+SpeakerTable.ID+" = "+id,null,null,null,null);
To explain a bit, the first argument String tableName can take table1, table2 as well safely, The second argument takes a String array of column names, I concatenated the two projections of the two classes. and finally, put by WHERE clause into the String selection argument.
Some days ago, I started building a Setting Screen for my Android app. Everything was fine, until I opened it on an older Android version. The overview screen had no material design, a thing I would have accepted, if there weren’t those completely destroyed dialogs: Android’s internal preferences are using Android’s internal app.AlertDialogs. Those dialogs in combination with the AppCompat Dialog Theme, which I had applied to them, resulted in a dialog with two frames on older devices (One system default dialog and a material frame around it).
So I decided to switch to the android.support.v7.preference library, only to face a lot more issues.
Including the Library
In order to use the new preferences, we need to import a library. To do so, we add this line to our gradle dependencies (You should change the version number to the latest).
At first, we need to create our preference structure: We create a new XML Android resource file as xml/app_preferences.xml. Now we can add our preference structure to this file. Make sure to add a unique android:keyattribute for each preference. More information: How to build the XML
The v7.preference library provides some preferences we can use: CheckBoxPreference, SwitchPreferenceCompat, EditTextPreference and a ListPreference (and a basic Preference). If we need more than these predefined preferences, we have to build them on our own.
Creating the Preference Fragment
Now we need to create our Preference Fragment, where we can show the preferences from our XML file. We do this by creating a new class, called SettingsFragment, which extends PreferenceFragmentCompat. Since the onCreatePreferences is declared as abstract in the source code of the library, we are forced to include our own implementation to tell the fragment to load our just created app_preferences.xml.
import android.support.v7.preference.PreferenceFragmentCompat;
public class SettingsFragment extends PreferenceFragmentCompat {
@Override
public void onCreatePreferences(Bundle bundle, String s) {
// Load the Preferences from the XML file
addPreferencesFromResource(R.xml.app_preferences);
}
}
We can add this SettingsFragment (v4.app.Fragment), like any other Fragment (e.g. with a FragmentTransaction) to an Activity.
Applying the Preference Theme
Finally we need to specify a preferenceTheme in our Activity’s theme. If we don’t do so, the App will crash with an IllegalStateException.
The v7.preference library provides only one Theme: PreferenceThemeOverlay(You may have a look at its source code). We add this with the following line in our Activity’s theme:
After we have done this, our result should now look like this.
(The Activity’s parent theme is Theme.AppCompat and the background is set with android:windowBackground)
As you can see, it has an oversized font and a horizontal line below the category title. This is definitely not material design.
It more looks like a mixture of material design for the CheckBoxand Switch widgets on the right and an old design for everything else.
This leads us to the next point: Applying the material theme to our settings.
Applying the Material Design Theme
Since there is no material theme in our current preference library, we need to import the v14.preference library. It is strange that Google splitted up these two libraries, because the v14 version is obviously only an addition to the v7.preference library. However, this means for us, that we have to add one more line to our gradle dependencies (You should change the version number to the latest).
Now we have access to two more themes: PreferenceThemeOverlay.v14 and PreferenceThemeOverlay.v14.Material (You may have a look at their source code). To use the material theme, we simply change the preferenceTheme in our Activity’s theme.
A side effect of including the v14.preference library is that we can use a new preference called MultiSelectListPreference (it requires the v7.preference library to work).
Our result should look like this. And this time it is full material design.
The font is not oversized anymore and also the horizontal line below the category title has disappeared.
We can change the color of the CheckBox, the Switch and the PreferenceCategory title by changing the colorAccent in our Activity’s theme.
This was only the first step to create a material design Settings Screen. As soon as you open the Alert Dialog of the EditText preference, you will find more design issues.
So earlier this year I attended a talk where the speaker wanted to introduce us to meaningful motion in android apps and he convinced us to use this in our apps as well. Motion came in with Material design, actually not really came but became popular with Material design and since google has added the same kind of motions to their apps as well, developers have started using it.
I love motion, not only does it boost engagement but it’s instantly noticeable. Think of the apps you use that feature motion design and how pleasing, satisfying, fluent and natural they feel to experience. Eg. Zomato, Play music etc.
Now think of some apps that don’t use any kind of motions and you’ll realise they look a bit boring and you as users will always prefer apps with some kind of motion.
Touch
So firstly let’s discover the feedback on touch. It helps to communicate to the user in a visual form that some interaction has been made. But also keep in mind that this animation should be enough for them to gain clarity and encourage further explorations and not distract them.
For adding backgrounds you can use the following :
?android:attr/selectableItemBackground — Show a ripple effect within the bounds of the view.
?android:attr/selectableItemBackgroundBorderless — Show a ripple effect extending the bounds of the view.
View Property Animator
Introduced in API 12, this allows us to perform animated operations (in parallel) on a number of view properties using a single Animator instance
Some of the parameters that can be added to a view are as follows :
alpha() -Set the alpha value to be animated to
scaleX() & scaleY()— Scales the view on it’s X and / or Y axis
setListener() — Set a listener for when the animation starts, ends, repeats or is cancelled.
Now let’s write some code on how to do this on a button for example:
mButton.animate().alpha(1f)
.scaleX(1f)
.scaleY(1f)
.translationZ(10f)
.setInterpolator(new FastOutSlowInInterpolator()) .setStartDelay(200)
.setListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) { }
@Override
public void onAnimationEnd(Animator animation) { }
@Override
public void onAnimationCancel(Animator animation) { }
@Override
public void onAnimationRepeat(Animator animation) { }
})
.start();
Note : Use ViewCompat class to implement the ViewPropertyAnimator from Android API version 4 and up
Object Animator
Similar to the ViewPropertyAnimator, the ObjectAnimator allows us to perform animations on various properties of the target view (both in code and XML resource files). However, there a couple of differences:
The ObjectAnimator only allows animations on a single property per instance e.g.Scale X followed by Scale Y.
However, it allows animations on a custom Property e.g. A view’s foreground colour.
Her we need to set the evaluator, set the delay and call start().
private void animateForegroundColor(@ColorInt final int targetColor) {
ObjectAnimator animator = ObjectAnimator.ofInt(YOUR_VIEW, FOREGROUND_COLOR, Color.TRANSPARENT, targetColor); animator.setEvaluator(new ArgbEvaluator()); animator.setStartDelay(DELAY_COLOR_CHANGE); animator.start();}
Interpolators
An Interpolator can be used to define the rate of change for an animation, meaning the speed, acceleration and behaviour during animating can be altered. Some of them are :
No Interpolator — The view animates with no rate of change alteration
Fast-Out Linear-In– The view begins animating and finishes in a linear motion
Fast-Out Slow-In– The view begins animating quickly and slows down to a finish
Linear-Out Slow-In– The view begins with a Linear motion and slows down to a finish
Accelerate-Decelerate– The view appears to accelerate at the start of the animation, and gradually decelerates when coming to a finish
These are some of the basics. there are a lot of other things like
Window transitions(Explode, fade, slide etc.)
Shared element Transitions
Other custom transitions
Animated Vector drawables
you can play around with these for a better understanding but be sure to actually try everything on a device/emulator since you’ll get to actually see the changes in the UI and in turn understand better.
Ever wondered how do people maintain different versions of the same app on play store with some customisations in each version. For example, Lite version and a pro version which signify free and paid versions with extra features in paid one.
With the arrival of gradle as a build tool, we got gradle flavors which is an excellent way to have some variations in your app versions.
It can also be leveraged to do hermetic testing with prod/mock flavors. Some other examples could be free/paid flavors and stable/experimental flavors etc.
Here prod and mock are two flavors and the customisations we add are that mock has a applicationIdSuffix. We can also add altogether different applicationIds as well as define a different version for different flavors and a lot of other different things
2. Now we sync the project
If you now open Build Variants tool window either using the quick access menu located in the status bar in the bottom left hand corner of the Android Studio main window or using the Build Variant tool window bar. Once loaded, clicking in the Build Variant cell for the app module should now list the four build variants: mockDebug, mockRelease and prodDebug, prodRelease
After this we get different folders corresponding to the different flavors. We go ahead and add the different folders where we want the changes to occur. For example if I want to show two different maps for two different versions, where one version is for fdroid and another one is for googleplay and hence we replace google maps with OpenStreet Maps in the fdroid version. Hence, We add res/values and res/layout folders for different resources and we also add different java folders and add the classes that would be different for both the flavors. Since both of these will require different set of permissions we can also add AndroidManifest.xml for these were we define the permissions etc. we want.
Not that we only add the changes for each file. All the other essential things can be added from the Main flavor of the android project.
Any variable available through your code
Another thing to know is that the you can have add variables like this so that you can use different variables for different flavors. For examle if you have different api’s and different options for each flavor like if you want to report crashes in one of the flavors and not in other. Here HOST variable is not the only one you can expose in your code. You can do it with whatever you want:
So I think this is a really useful thing to know and is used in almost every app. We all need some configuration differences in our apps and gradle flavors is the best way to go about it. Go nuts with your imagination on the usage of flavors 😛
Lambda Expressions are one of the most important features added to Java 8. Prior to Lambda Expressions, implementing functional interfaces i.e interfaces with only one abstract method has been done using syntax that has a lot of boilerplate code in it. In cases like this, what we are trying to do is pass a functionality as an argument to a method, such as what happens when a button is clicked.
Lambda expressions enables you to do just that, in a way that is much more compact and clear.
Syntax of Lambda Expressions
A lambda expression consist of the following:
A comma separated list of formal parameters enclosed in parentheses. The data types of the parameters in a lambda expression can be omitted. Also the parenthesis can be omitted if there is only one parameter. For example:
The arrow token ->
A body which contains a single expression or a statement block. If a single expression is specified, the java runtime evaluates the expression and then return its value. To specify a statement block, enclose statements in curly braces "{}"
Lambda Expressions in Android
To use Lambda Expressions and other Java 8 features in Android, you need to use the Jack tool-chain. Open your module level build.gradle file and add the following:
Sync your build.gradle file and if you are having any issue with build tools, you may need to update buildToolsVersion in your build.gradle file to "24rc4" or just download the latest Android SDK Build-tools from the SDK Manager, under the Tools (Preview channel).
Example
Adding a click listener to a button
without lambda expression
with lambda expressions It is as simple as:
As we can see above, using lambda expressions makes implementing a functional interface clearer and compact. Standard functional interfaces can be found in the java.util.function package [included in Java 8]. These interfaces can be used as target types for lambda expressions and method references.
Google released the new Awareness API for everyone, a suite of signals concurring to gives the developer the context in which our user is, all as part of the Play Services 9.2.0, already available on Android devices around the globe.
This library combines 7 different feeds managing both the battery drain and the used memory, providing us with data such as user location, weather, time, headphones connection status, places, nearby beacons and currently performing activity.
INTRO
The Awareness API comes with two different versions, one is a callback based one (Fence API) and the other one is a polling based one (Snapshot API): while the first one will notify us when the conditions we specified are met, the second expects us to query the suite for the data from a unified interface.
There are 7 kinds of context that we can work with in Awareness API such as
Time — Local time at current user’s location
Location — Latitude/Longitude
Place — Places around user
Activity — Detected user activity (biking, walking, running, etc.)
Beacons — Check nearby beacon(s)
Headphones — Are headphones plugged in?
Weather — Current weather conditions
Now the Two set’s of API’s :
Snapshot API — Allows you to “request an information based on user’s context” as listed above.
Fence API — Allows you to “receive a signal when user’s context has changed and reaches the condition” through callback function, for example, if user moves closed to the specific coordinate with headphones plugged in, Fench API will call the registered BroadcastReceiver and let you do your job.
And then browse to API Manager page and search for Awareness and click at Awareness API and Click Enable and wait until it finishes enabling
3. Go to Credentials tab and click at Create credentials -> API key -> Android key. Enter the name you project, for example, Android key and click Create (or if you have already created Android key previously, you could skip this step and use the existed one)
So this ends the setup part. Now we move on to the actual data retrieval from the API.
Accessing Snapshot API
Awareness.SnapshotApi.getDetectedActivity(mGoogleApiClient)
.setResultCallback(new ResultCallback<DetectedActivityResult>() {
@Override
public void onResult(@NonNull DetectedActivityResult detectedActivityResult) {
if (!detectedActivityResult.getStatus().isSuccess()) {
Log.e(TAG, "Could not get the current activity.");
return;
}
ActivityRecognitionResult ar = detectedActivityResult.getActivityRecognitionResult();
DetectedActivity probableActivity = ar.getMostProbableActivity();
Log.i(TAG, probableActivity.toString());
}
});
This will return the most probable activity done by the user. We can get a particular activity like walking, running, driving as well by integers defined below
public static final int IN_VEHICLE = 0;
public static final int ON_BICYCLE = 1;
public static final int ON_FOOT = 2;
public static final int STILL = 3;
public static final int UNKNOWN = 4;
public static final int TILTING = 5;
public static final int WALKING = 7;
public static final int RUNNING = 8;
Similarly we can also get Headphone state, location, weather, beacon etc.
For example let’s see headphone state:
Awareness.SnapshotApi.getHeadphoneState(mGoogleApiClient)
.setResultCallback(new ResultCallback<HeadphoneStateResult>() {
@Override
public void onResult(@NonNull HeadphoneStateResult headphoneStateResult) {
if (!headphoneStateResult.getStatus().isSuccess()) {
Log.e(TAG, "Could not get headphone state.");
return;
}
HeadphoneState headphoneState = headphoneStateResult.getHeadphoneState();
if (headphoneState.getState() == HeadphoneState.PLUGGED_IN) {
Log.i(TAG, "Headphones are plugged in.\n");
} else {
Log.i(TAG, "Headphones are NOT plugged in.\n");
}
}
});
This is same as acquiring activity and headphoneState.getState() will give you if it is plugged in or not
Now we take a look at the Fence API
Fence is similar to the geofence but in addition to geofence, the fence API also allows us to set awareness conditions and check if both conditions are true.
Here when we get message in onReceive() and we can detect if headphone is connected or not. Something like this
FenceState fenceState = FenceState.extract(intent);
Log.d(TAG, "Fence Receiver Received");
if (TextUtils.equals(fenceState.getFenceKey(), "headphoneFenceKey")) {
switch (fenceState.getCurrentState()) {
case FenceState.TRUE:
Log.i(TAG, "Fence > Headphones are plugged in.");
break;
case FenceState.FALSE:
Log.i(TAG, "Fence > Headphones are NOT plugged in.");
break;
case FenceState.UNKNOWN:
Log.i(TAG, "Fence > The headphone fence is in an unknown state.");
break;
}
}
So as you can see this is pretty straight forward and very useful. You can build so many apps with multiple fences. You can think of a lot of usecases for this and make a lot of amazing apps. Happy Tinkering with the Awareness API!
Hey Guys I recently used Bottom sheets, so that I should write a blog about it because I don’t see a lot of developers using this in their app UI’s.
It’s a very interesting UI element. A Bottom Sheet is a sheet of material that slides up from the bottom edge of the screen. Displayed only as a result of a user-initiated action, and can be swiped up to reveal additional content. It can be a temporary modal surface or a persistent structural element of an app.
This component was introduced in the Android Design Support library 23.2. Many apps like Google Maps use the bottom sheet, in which a sliding window pops up from the bottom of the screen. Also the Google play music app uses. When we drag up the sheet we see the song details as well as the current playlist.
There are 3 states of Bottom sheets :-
Expanded — When the sheet is completely visible.
Collapsed — When the sheet is partially visible.
Hidden — When the sheet is completely hidden.
Step 1 is we need to import the latest design support library. Put this line in your app level build.gradle file.
compile ‘com.android.support:design:23.2.0’
Then one should create a new Blank Activity (not Empty Activity) in Android Studio. It sets up the CoordinatorLayout by default.
So now there ate two layouts created by default namely activity_main.xml and content_main.xml.
Those who aren’t familiar with the coordinator layout — basically there is a base level layout activity_main which contains the FloatingButton and within this layout including content_main.xml which will contain the rest of the layout. Notice that one also has to include bottomsheet_main.xml. This layout contains our bottom sheet layout which will be created next.
Create a new layout file called bottomsheet_main.xml
This layout is how our bottom sheet will actually look. You can design it as you want.
Now for the actual java code. This is the easiest part actually. Just set listeners to the 3 buttons created and perform the corresponding action with the bottom sheet.
public class MainActivity extends AppCompatActivity {
BottomSheetBehavior bottomSheetBehavior;
Button open, collapse, hide;
TextView heading;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
View bottomSheet = findViewById(R.id.bottom_sheet);
bottomSheetBehavior = BottomSheetBehavior.from(bottomSheet);
setup();
}
private void setup() {
open = (Button) findViewById(R.id.open);
collapse = (Button) findViewById(R.id.collapse);
hide = (Button) findViewById(R.id.hide);
heading = (TextView) findViewById(R.id.heading);
//Handling movement of bottom sheets from buttons
open.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
heading.setText("Welcome");
heading.setTextColor(ContextCompat.getColor(MainActivity.this, R.color.colorPrimary));
}
});
collapse.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
heading.setText("Collapsed");
heading.setTextColor(ContextCompat.getColor(MainActivity.this, R.color.colorAccent));
}
});
hide.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
}
});
//Handling movement of bottom sheets from sliding
bottomSheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
@Override
public void onStateChanged(View bottomSheet, int newState) {
if (newState == BottomSheetBehavior.STATE_COLLAPSED) {
heading.setText("Collapsed");
heading.setTextColor(ContextCompat.getColor(MainActivity.this, R.color.colorAccent));
} else if (newState == BottomSheetBehavior.STATE_EXPANDED) {
heading.setText("Welcome");
heading.setTextColor(ContextCompat.getColor(MainActivity.this, R.color.colorPrimary));
}
}
@Override
public void onSlide(View bottomSheet, float slideOffset) {}
});
}
}
We just use the bottomSheetBehavior.setState() method to set the relevant state on each button click.
The bottom sheets can also be dragged by touch gestures. One can simply touch the sheet and drag them up or down. For these touch gestures to be handled one has to implement the onStateChanged() listener. This listener is fired everytime the state of the sheet changes by gestures. Whenever this triggers it checks the state of the bottom sheet and again do the desired action which user would have done by the button clicks.
As you can see, this is a pretty neat UI solution and can be implemented so easily. Go try it out for yourself. Adios!
Few months ago, during the Google I/O conference, Google introduced a new set of tools for Android developers. Among them is a new Layout editor and a new layout called the ConstraintLayout.
I’ll be highlighting the key points in this brand new layout.
ConstraintLayout is available in a new Support Library that’s compatible with Android 2.3 (Gingerbread) and higher, but the new layout editor is available only in Android Studio 2.2 Preview.
Layout Editor & Constraints Overview.
The new layout editor in Android Studio 2.2 Preview is specially built for the ConstraintLayout. You can specify the constraints manually, or automatically reference within the layout editor.
Overview of Constraints?
A constraint is the description of how a view should be positioned relative to other items, in a layout. A constraint is typically defined for one or more sides by connecting the view to:
An anchor point, or another view,
An edge of the layout,
Or An invisible guide line.
Since each view within the layout is defined by associations to other views within the layout, it’s easier to achieve flat hierarchy for complex layouts.
In principle, the ConstraintLayout works very similar to the RelativeLayout, but uses various handles (or say anchors) for the constraints.
Resize handle. The resize handle is the seen in the corners of the figure above, and it’s used to resize the view.
Side handle. The side handle is the in the figure above, and it’s used to specify the location of a widget. E.g using the left side handle to always be aligned to the right of another view, or the left of the ConstraintLayout itself.
Baseline handle. The baseline handle is the in the figure above. It is used to align the text of a view by the baseline of the text on another view.
Getting started with ConstraintLayout
Setup
Ensure that you’re running the AS 2.2 preview, and Android Support Repository version 32 or higher, it’s required before you can use the ConstraintLayout. Let’s get started.
First, you need to add the constraint layout library to your app’s dependencies within your build.gradle file:
There are typically two ways to create ConstraintLayout in AS. You can create a new XML layout and select the root element to be a ConstraintLayout or convert an existing layout into a ConstraintLayout as shown in the image below:
Once you have the ConstraintLayout setup, what is next is to add the constraints to the views within that layout.
As an example, drag an ImageView to the layout. The new layout builder will immediately ask to add a drawable or resource, select one from the options and press ok. Also drag a TextView unto the layout.
To create a constraint, drag the top side handle on the ImageView to the top of the ConstraintLayout. You can also drag from the top side handle of the TextView to the bottom handle of the ImageView
Using the Inspector Pane
Now that we’re able to add constraints, we will need to use the inspector. It’s on the right hand side of the layout builder and it lists various properties of the selected widget. Typically, it looks as shown below:
You can use the sliders to move the view by percentage along the x and y axes. You can also control the dimensions of the view from the inspector pane, by altering the values corresponding to the layout_width and layout_height fields.
Taking a closer look at the square widget on the inspector pane. It contains some more control over the dimensions of the views.
There are other modes of controlling the size of the view. Clicking on the inner lines in the image above help you cycle through the other modes.
Fixed mode: This allows you specify the width and height of the view.
Any size: This mode allows the image to fill up all the space required to fulfill that constraint. You can look at this like “match constraint”
Wrap content: This just expands to fill the content of the view. E.g text or image
Using Auto-connect to add constraints.
Autoconnect as the name suggests, automatically creates connections between views/widgets. It tries to create connections to neighboring views/widgets.
To enable autoconnect, look out for the icon on the top bar of the layout editor.
You must be logged in to post a comment.