Tuesday, February 28, 2017
Monday, February 27, 2017
10 Examples of Animation on CodePen You Can Learn From
CodePen is a great place to find inspiration and see what crazy UI experiments others are coming up with. As well as this, it’s also a useful place to find educational content. In this roundup we’ll explore some cool examples of CodePens that teach us all about web animation.
1. How Keyframes Work
This CodePen illustrates how browsers read through the sequence of steps in a keyframe
animation.
2. Performant CSS Animation for Beginners
When we animate with CSS we have a vast array of properties to animate. However, not all are a good idea. Animating the wrong properties can make for slow, janky animations. This example from Ian Hazelton explains how the most efficient four properties work.
3. Keyframes Animation Demo
Sometimes a simple example is all you need. This example shows how the from
and to
syntax can be used in a simple keyframe.
4. requestAnimationFrame Demo
In this JavaScript demo from Matt West, see how the requestAnimationFrame
API can be used to play and pause.
5. How to ChartJS
A great use of inline comments that demonstrate how to create two animated charts using ChartJS.
6. HTML5 Canvas Guide for Beginners
A nicely commented guide to using Canvas, by Petr Tichy.
7. Convert GIF to CSS Animations
Sometimes you don’t need a rendered GIF to get a fancy animation effect. These CSS-only examples from Joey Cheng are a useful example of what’s possible.
8. animateTransform Examples
Chris Coyier demonstrates how to use SVG’s animateTransform
.
9. CSS Transforms: 3D Example and TransitionEnd
It’s often useful to know when an animation has finished. This simple demo shows how we can use JavaScript to tell when an animation ends.
10. Switching Animation Keyframes in Media Queries
Another great one to bookmark–this example shows how we can change animations using media
queries. Resize the screen to see the effect!
Bonus: CSS Motion Paths
Lastly, let’s glimpse into the future of SVG animation with this demo showing how the motion-path
property allows us to create complex paths of motion for our animations.
That’s a Wrap!
These are just a handful of the demos, blogs and walkthroughs available on CodePen. CodePen isn’t just great for animation though; try searching and you’ll find all sorts of great info and demos covering all manner of web development topics. Explore, learn, and have fun!
More Animation Resources
- Get Started With Web Animation
- Adding Appeal to Your Animations on the Web by yours truly
- 9 Popular Courses on CSS Animation
- New Short Course: A Visual Guide to CSS Animation by Kezz Bracey
- Ease in to Cubic Bezier Functions in Our Coffee Break Course by Guy Routledge
Sunday, February 26, 2017
Saturday, February 25, 2017
Friday, February 24, 2017
Thursday, February 23, 2017
Wednesday, February 22, 2017
Tuesday, February 21, 2017
Getting Started With Cassandra: CQL Data Types and Using GoCQL
In the first part of this tutorial series, I covered the very basics of Cassandra and used CQLSH to communicate with the database system via shell. In this second part, I will cover in brief the major datatypes available in CQL. Then I will cover the essentials of gocql
, a Golang client package which implements the Cassandra driver for Golang. I will cover how to create a session connection with Cassandra with some configuration options and then how to run various queries using the session.
Cassandra provides support for basic datatypes which are available in almost all database systems. Apart from this, it also provides for complex collection types which can store combinations of simple data in the form of list, set, and map. Apart from this, CQL also has support for user-defined types, allowing developers to have their own datatypes which are easy to read and understand.
Basic Data Types
- ascii: Represents a string of ASCII characters. Insertion of any non-ASCII character into a column of this type would result in an error.
- bigint: Represents a 64-bit signed long. Used to store long numbers. This should be used only when we are sure we need such long numbers because this occupies more space as compared to int.
- blob: Used to store arbitrary bytes. This is represented as hexadecimal, and any data without any validation can be stored in this field.
- boolean: Stores
true
orfalse
.
- counter: Represents a 64-bit signed integer, but the value of this column cannot be set. There are only two operations on this column, increment and decrement. In a table with a counter column, only counter types and primary key are allowed. There are no
INSERT
statements allowed in a table with counter column(s); onlyUPDATE
can be used. For example:
> CREATE TABLE website_tracker ( id int PRIMARY KEY, url text, visitor_count counter ); > UPDATE website_tracker SET visitor_count = visitor_count + 1 WHERE id = 1; > SELECT * FROM website_tracker; id | url | count ----+------+------ 1 | a.com | 1 (1 rows)
- date: Represents a date value without a time value. Cassandra encodes the same as an integer value since epoch. Dates can be represented as strings in format
yyyy-mm-dd
.
- decimal: Represents a variable-precision decimal value. Best for storing currency or financial values.
- double: Stores a 64-bit floating point value.
- float: Stores a 32-bit floating point value.
- inet: Represents an IP address string in IPv4 or IPv6 format.
- int: Represents a 32-bit signed integer. Used mostly when storing integer values.
- smallint: Represents a 2-byte (16-bit) integer. Can be preferred over int for storing small integer values to save space.
- text: Represents a UTF-8 encoded string. Should be used when we want to store non-ASCII characters.
- time: Represents a time value. Represented as a string in the format
01:02:03.123
and stored 64-bit signed integer which represents nanoseconds elapsed since midnight.
- timestamp: Stores both date and time components with millisecond precision. Can be represented as text in the format
2016-12-01 01:02:03.123
.
- tinyint: Represents a 1-byte (8-bit) integer. Can be preferred over int or smallint for storing small integer values to save space.
- timeuuid: Stores version 1 UUID.
- uuid: UUID in standard format. This is a larger value as compared to timeuuid.
- varchar: Similar to text. Both can be used interchangeably.
- variant: An integer value with arbitrary precision. It is advised to use a datatype with required precision.
Collection Data Types
- set: This type stores a collection of values. The values are stored as unordered, but CQLSH would return them in a sorted manner. For example, strings would be sorted alphabetically. Let's modify the table we created above:
> ALTER TABLE website_tracker ADD tagsSet set<text>; > UPDATE website_tracker SET tagsSet = {'tag1'} WHERE id = 1; > SELECT tagsSet FROM website_tracker WHERE id = 1; tagsSet ---------- {'tag1'} > UPDATE website_tracker SET tagsSet = tagsSet + {'gat2'} WHERE id = 1; > SELECT tagsSet FROM website_tracker WHERE id = 1; tagsSet ------------------ {'gat2', 'tag1'}
You can use the usual set operations like difference
to remove elements. To clear out or replace the complete set, do SET tags = {<something>}
.
- list: A list also stores a collection of values but stores them in ordered fashion, which is by the order of insertion by default. Let's try to do the same thing that we did above with sets with a list now:
> ALTER TABLE website_tracker ADD tagsList list<text>; > UPDATE website_tracker SET tagsList = ['tag1'] WHERE id = 1; > SELECT tagsList FROM website_tracker WHERE id = 1; tagsList ---------- ['tag1'] > UPDATE website_tracker SET tagsList = tagsList + ['gat2'] WHERE id = 1; > SELECT tagsList FROM website_tracker WHERE id = 1; tagsList ------------------ ['tag1', 'gat2']
In a list, values can be prepended, subtracted (as in sets), inserted/replaced/deleted by index value (SET tags[1] = '<somevalue>'
), etc.
- map: A map contains a collection of key-value pairs. These can be anything except a counter type. Let's have a small description for each tag.
> ALTER TABLE website_tracker ADD tagsMap map<text, text>; > UPDATE website_tracker SET tagsMap = {'tag1': 'Tag One'} WHERE id = 1; > SELECT tagsMap FROM website_tracker WHERE id = 1; tagsMap ---------------------- {'tag1': 'Tag One'} > UPDATE website_tracker SET tagsMap['tag2'] = 'Tag Two' WHERE id = 1; > SELECT tagsMap FROM website_tracker WHERE id = 1; tagsMap ------------------ {'tag1': 'Tag One', 'tag2': 'Tag Two'}
User-Defined Data Types
It is possible in Cassandra to define our own types. This gives a lot of flexibility and makes overall maintenance of data easier. Let's say we want to store the registration address of the website.
> CREATE TYPE address ( ... street text, ... city text, ... state text); > ALTER TABLE website_tracker ADD reg_address address;
In order to use a user-defined type in a nested collection, we need to specify it as a frozen
collection.
> ALTER TABLE website_tracker ADD reg_addresses map<text, frozen<address>>;
Using GoCQL
I am assuming that you have some knowledge of using Golang and configuring and installing packages.
Installation
To install the gocql
package, run the following command from shell:
$ go get http://ift.tt/1cXjEki
Now I will create a Go script which will explain the concepts needed to understand gocql
.
Writing the Script
main.go
package main import ( "http://ift.tt/1cXjEki" "log" "time" ) func PerformOperations() { // Provide the cassandra cluster instance here. cluster := gocql.NewCluster("127.0.0.1") // The authenticator is needed if password authentication is // enabled for your Cassandra installation. If not, this can // be removed. cluster.Authenticator = gocql.PasswordAuthenticator{ Username: "some_username", Password: "some_password", } // gocql requires the keyspace to be provided before the session is created. // In future there might be provisions to do this later. cluster.Keyspace = "keyspace_name" // This is time after which the creation of session call would timeout. // This can be customised as needed. cluster.Timeout = 5 * time.Second cluster.ProtoVersion = 4 session, err := cluster.CreateSession() if err != nil { log.Fatalf("Could not connect to cassandra cluster: %v", err) } // Check if the table already exists. Create if table does not exist keySpaceMeta, _ := session.KeyspaceMetadata("keyspace_name") if _, exists := keySpaceMeta.Tables["person"]; exists != true { // Create a table session.Query("CREATE TABLE person (" + "id text, name text, phone text, " + "PRIMARY KEY (id))").Exec() } // DIY: Update table with something if it already exist. // Insert record into table using prepared statements session.Query("INSERT INTO person (id, name, phone) VALUES (?, ?, ?)", "shalabh", "Shalabh Aggarwal", "1234567890").Exec() // DIY: Update existing record // Select record and run some process on data fetched var name string var phone string if err := session.Query( "SELECT name, phone FROM person WHERE id='shalabh'").Scan( &name, &phone); err != nil { if err != gocql.ErrNotFound { log.Fatalf("Query failed: %v", err) } } log.Printf("Name: %v", name) log.Printf("Phone: %v", phone) // Fetch multiple rows and run process over them iter := session.Query("SELECT name, phone FROM person").Iter() for iter.Scan(&name, &phone) { log.Printf("Iter Name: %v", name) log.Printf("Iter Phone: %v", phone) } // DIY: Delete record } func main() { PerformOperations() }
Most of the working concepts are explained in the above code itself. Some points worth noting are the different operations used along with session.Query()
. Apart from the three operations below, there are many more supported which can be seen in the documentation.
Exec()
: This would just execute the query without returning any rows. Returns error if any.Scan()
: This would execute the query while copying the values of columns from the first row matched in the query to the variables passed. It would discard any rows apart from the first one.Iter()
: This would execute the query and return an iterator which would then just work like howScan()
works for each row fetched.
Running the Script
To run the script, execute the command below in shell.
$ go run main.go 2017/02/03 12:53:40 Name: Shalabh Aggarwal 2017/02/03 12:53:40 Phone: 1234567890 2017/02/03 12:53:40 Iter Name: Shalabh Aggarwal 2017/02/03 12:53:40 Iter Phone: 1234567890
Conclusion
In this second part of this tutorial series, we covered various built-in data types available with Cassandra. We also saw how collection types work and how user-defined types can be used to make an overall schema flexible. We also saw how we can interact with Cassandra programmatically in Golang using gocql. This package offers a lot more functionality which can be explored on your own.
Monday, February 20, 2017
Getting Started With Crafty: The Game Loop
Up to this point in this series, you have learned how to manipulate different entities and use the keyboard to move them around. In this part, you will learn how to use the game loop in Crafty to continuously check for various events and animate different entities.
The game loop in Crafty is implemented in Crafty.timer.step, which uses global events to communicate with the rest of the engines. The loop is driven by requestAnimationFrame when available. Each loop consists of one or more calls to the EnterFrame
event and a single call to RenderScene
which results in the redrawing of each layer.
The final value of all the properties and variables is resolved before a scene is rendered. For example, if you move your player 5 px to the right ten times inside a single EnterFrame
event, it will be directly drawn 50 px to the right by skipping all the intermediate drawings.
EnterFrame and RenderScene
Everything in your game that needs to change over time is ultimately linked to the EnterFrame
event. You can use the .bind()
method to bind different entities to this event. Functions bound to this event are also passed an object with properties like dt
which determines the number of milliseconds that have passed since the last EnterFrame
event.
You can use the dt
property to provide a smooth gaming experience by determining how far the game state should advance.
The RenderScene
event is used to make sure that everything visible on the screen matches the current state of the game at the last EnterFrame
event. Normally, you will not need to bind this event yourself unless you decide to implement your own custom rendering layer.
Using Tween to Animate 2D Properties
You can use the Tween
component when you just want to animate the 2D properties of an entity over a specific period of time. You can animate the x
, y
, w
, h
, rotation
, and alpha
properties using this component. Let's animate the x value and height of the orange and black boxes that you've been creating in the last two tutorials.
Here is the code you need:
blackBox.tween({x: 500}, 3000); orangeBox.tween({x: 50, h: 100, rotation: 360}, 3000);
You've probably noticed that the orange box is not rotating around its center but its top left corner. You can change the rotation center by using the .origin()
method. It can accept two integer arguments, which determine the pixel offset of origin in the x and y axes.
It also accepts a string value as its argument. The string value can be a combination of center, top, bottom, middle, left, and right. For example, .origin("center")
will rotate the entity around its center, and .origin("bottom right")
will rotate the entity around the bottom right corner.
You can pause or resume all tweens associated with a given entity using the .pauseTweens()
and .resumeTweens()
methods. Similarly, you can also use .cancelTween()
to cancel a specific tween.
Understanding the Crafty Timer
The Crafty.timer
object handles all the game ticks in Crafty. You can use the .FPS()
method with this object to get the target frame rate. Keep in mind that it is not the actual frame rate.
You can also use the .simulateFrames(Number frames[, Number timestep])
method to advance the game state by a given number of frames. The timestep
is the duration to pass each frame. If it is not specified, a default value of 20ms is used.
Another useful method is .step()
, which will advance the game by performing a step. A single step can consist of one or more frames followed by a render. The number of frames will depend on the timer's steptype
. This method triggers a variety of events like EnterFrame
and ExitFrame
for each frame and PreRender
, RenderScene
, and PostRender
events for each render.
There are three different modes of steptype
: fixed
, variable
, and semifixed
. In fixed
mode, each frame in Crafty is sent the same value of dt
. However, this steptype
can trigger multiple frames before each render to achieve the target game speed.
You can also trigger just one frame before each render by using the variable
mode. In this case, the value of dt
is equal to the actual time elapsed since the last frame.
Finally, the semifixed
mode triggers multiple frames per render, and the time since last frame is equally divided among them.
Creating a Very Basic Game
If you have read all the tutorials in this series, you should have gained enough knowledge by now to create a very basic game. In this section, you will learn how to put everything you learned to use and create a game where the main player has to eat a piece of food.
The food will be a rotating red square. As soon as the food comes in contact with the player, it disappears from the old location and spawns in a new random location. The player can be moved using A, W, S, D, or the arrow keys.
One more thing that you need to take care of is the position of the player. It is supposed to stay with the bounds of the game stage.
Let's write the code for the food first:
var foodBox = Crafty.e("2D, Canvas, Color, Food") .attr({x: 150, y: 250, w: 15, h: 15}) .color("red") .origin("center") .bind("EnterFrame", function(eventData) { this.rotation += 4; });
By default, Crafty would have used the top left corner of the food entity to rotate it. Setting origin to center makes sure that the food entity rotates around its center.
var playerBox = Crafty.e("2D, Canvas, Color, Fourway, Collision") .attr({x: 50, y: 360, w: 50, h: 50}) .color("black") .fourway(200) .bind("EnterFrame", function(eventData) { if(this.x < 0) { this.x = 0; } if(this.y < 0) { this.y = 0; } if(this.x > (stageWidth - this.w)) { this.x = stageWidth - this.w; } if(this.y > (stageHeight - this.h)) { this.y = stageHeight - this.h; } });
The player entity checks the current location of the player in each frame and resets the location if the player tries to go outside the game stage.
You can use a Text
entity to keep track of the score. The score is shown in the top left corner. The gameScore
variable stores the number of times the player hits the food entity.
var scoreText = Crafty.e('2D, DOM, Text') .attr({ x: 10, y: 10 }) .textFont({ size: '25px' }); scoreText.text(gameScore.toString());
Now, you just have to write the code to move the food to a different location when a hit is detected. The following code will do exactly that.
playerBox.checkHits("Food").bind("HitOn", function(hitData) { foodBox.x = Math.random() * (stageWidth - foodBox.w); foodBox.y = Math.random() * (stageHeight - foodBox.h); gameScore += 1; scoreText.text(gameScore.toString()); });
You need to keep in mind that you subtract the width and height of your food entity from the stage width and height respectively. This makes sure that the food is always completely inside the stage. Here is a demo of the game:
Final Thoughts
With the help of Crafty, you have created a very basic game by writing a few lines of code. Right now, the game lacks a few features which could make it more interesting. First, there are no sounds. Second, there is no way for the player to get out, and the difficulty level also remains the same throughout the game. You will learn about sound, sprites, mouse events, and other features of the library in the next series.
If you had any problems or doubts while going through all the examples in the series, let me know in the comments.
Sunday, February 19, 2017
Friday, February 17, 2017
Getting Started With Crafty: Controls, Events, and Text
In the last tutorial, you learned about entities in Crafty and how you can manipulate them using different methods. In this tutorial, you will learn about different components that will allow you to move different entities around using the keyboard.
Crafty has three different components to move elements around. These are Twoway
, Fourway
, and Multiway
. This tutorial will introduce you to all these components. In the end, you will learn about the Keyboard
component and various methods associated with it.
Twoway and Fourway
The Twoway
component allows an entity to move left or right using arrow keys or A and D. It also allows the entity to jump using the up arrow or the W key. You will have to add a Gravity
component to your entities to make them jump.
The .twoway()
method accepts two arguments. The first one determines the speed of the entity in the horizontal direction, while the second argument determines the jump speed of the entity. Leaving out the second argument will set the value of the jump speed equal to twice the speed in the horizontal direction.
The Fourway
component will allow an entity to move in four different directions by either using the arrow keys or W, A, S, D. The .fourway()
method only accepts one argument, which will determine the speed of the given entity in all directions.
Multiway
One major drawback of the Fourway
component is that it does not allow you to set different speeds for the horizontal and vertical directions.
On the other hand, the Multiway
component allows you to set the speed in each axis individually. It also allows you to assign different keys to move the entity in different directions. The first argument in the .multiway()
method is the speed of our entity. The second argument is an object to determine which key will move the entity in which direction.
The directions are specified in degrees. 180 is left, 0 is right, -90 is up, and 90 is down. Here are a few examples:
blackBox.multiway({x:150,y:75}, {W: -90, S: 90, D: 0, A: 180}); orangeBox.multiway(150, {I: -90, K: 90, L: 0, J: 180}); purpleBox.multiway(150, {Y: -45, G: -135, B: 135, H: 45});
The code above sets the speed of the black box equal to 150 in the horizontal direction and 75 in the vertical direction. The orange box moves at a speed of 150 in all directions but has been assigned different keys for movement. The purple box does not move strictly horizontally or vertically but at a 45-degree angle. The speed here is in pixels per second.
Basically, you can assign any key to any direction using the Multiway
component. This can be very helpful when you want to move multiple entities independently.
This component also has a .speed()
method, which can be used to change the speed of an entity at a later time. You can also disable the key controls at any time using the .disableControl()
method.
The Keyboard Component
The three components in the previous sections allow you to move an entity around using different keys. However, you might want more control over what happens when a key is pressed. For example, you might want to make the player bigger once a specific key is pressed or make the player more powerful once another key is pressed. This can be achieved using the Keyboard
component.
This component also gives you access to the .isDown(String keyName/KeyCode)
method, which will return true or false based on whether the key pressed has the given KeyName
or KeyCode
.
Crafty also has two different keyboard events, KeyDown
and KeyUp
. You can bind these events to any entity in your game without using the Keyboard
component. The KeyDown
event is triggered whenever the DOM keydown
event occurs. Similarly, the KeyUp
event is triggered whenever the DOM keyup
event occurs.
blackBox.bind('KeyDown', function() { if (this.isDown('L')) { this.w += 5; } }); orangeBox.bind('KeyDown', function(e) { if (e.key == Crafty.keys.K) { this.h += 5; } });
In the above code, the blackBox
already had the Keyboard
component. This allowed us to use the .isDown()
method to determine the key pressed.
Try pressing L and K in the following demo to increase the width and height of the two boxes respectively.
The Text Component
It is very easy to add text to your game using Crafty. First, you need to create an entity with the Text
component. Then, you can add text to your entity using the .text()
method, which accepts a string as its parameter.
The location of the text can be controlled by using the .attr()
method to set the value of x
and y
co-ordinates. Similarly, the color of the text can be specified using the .textColor()
method. A few other properties like the size
, weight
, and family
of the font can be set using the .textFont()
method. Here is an example:
var playerName = Crafty.e('2D, DOM, Text') .attr({ x: 10, y: 10 }); playerName.text('ZombieHunter'); playerName.textColor('red');
As I mentioned earlier, the .text()
method requires you to supply a string as parameter. This means that if the game score is a number, you will have to convert it to a string for the .text()
method to render it.
Most of the 2D properties and methods will work without any issue with the Text
component. For example, you can rotate and move it around with ease. However, there are two things that you need to keep in mind. Styling of the text works best when rendered using the DOM. When rendered on Canvas, the width and height of the text entity will be set automatically.
Final Thoughts
Using the knowledge from this and the last tutorial, you should now be able to move different entities around using the keyboard. You can also change the appearance of the main player and other entities based on the different keys pressed.
If you have any questions about this tutorial, let me know in the comments.
Thursday, February 16, 2017
Getting Started With Crafty: Entities
In the last tutorial, you learned about the basics of entities and how they are the building blocks in your game. In this tutorial, you will go beyond the basics and learn about entities in more detail.
Entities and Their Components
Every entity is made up of different components. Each of these components adds its own functionality to the entity. Crafty offers a lot of built-in components, but you can also create your own custom components using Crafty.c()
.
You learned about a few basic components like 2D
, Canvas
, Color
, and Fourway
in the first tutorial. Let's begin by creating another entity using these components:
var playerBox = Crafty.e("2D, Canvas, Color, Fourway") .attr({ x: 50, y: 50, w: 50, h: 50 }) .color("black") .fourway(200);
When you have a lot of entities with different components, it might become necessary to know if a given entity has a specific component attached to it. This can be accomplished by using the .has()
method.
Similarly, you can add components to an entity by using .addComponent()
. To add multiple components at once, you can pass them as a string with different components separated by commas or pass each component as different argument. Nothing will happen if the entity already has the component that you are trying to add.
playerBox.addComponent("Jumper, Gravity, Collision"); playerBox.addComponent("Jumper", "Gravity" , "Collision");
You can also remove components from an entity using .removeComponent(String Component[, soft])
. This method takes two arguments. The first one is the component that you want to remove, and the second argument determines whether the element should be soft removed or hard removed. Soft removal will only cause .has()
to return false
when queried for that specific component. A hard removal will remove all the associated properties and methods of that component.
By default, all components are soft removed. You can set the second argument to false
to hard remove a component.
Setting Values for Different Attributes
You will probably need to set different values for specific attributes of all the entities in your game. For example, an entity that represents the food of the main player in the game should look different than the entity that represents the player itself. Similarly, a power up is going to look different than food entities. Crafty allows you to set the values of different entities in a game either separately or all at once using .attr()
.
The main entity is currently stored in the playerBox
variable. So you can set value of different properties directly using the following code:
playerBox.x = 50; playerBox.y = 50; playerBox.z = 2; playerBox.attr({ x:50, y:50, z:2 })
The z
property sets the z-index of different entities. An entity with higher z
value will be drawn over another one with a lower value. Keep in mind that only integers are allowed as valid z-index values.
Let's create a food entity with smaller size, a different position, and rotation applied to it. The rotation is specified in degrees, and using a negative value rotates the entity in counterclockwise direction.
var foodBox = Crafty.e("2D, Canvas, Color, Food") .attr({ x: 150, y: 250, w: 10, h: 10 }) .color("red"); foodBox.attr({ z:1, rotation: 45 });
As you can see in the demo below, both the food and main player are easily distinguishable now. If you try to move the main player over the food, you will see that the food is now drawn below the main player because of a lower z-index.
Binding Events to Entities
There are a lot of events that you may need to respond to while developing a game. For example, you will have to bind the player entity to a KeyDown
event if you want it to grow in size when a specific key is pressed. Crafty allows you to bind different entities to specific events using the .bind()
method. Here is an example:
playerBox.bind('KeyDown', function(e) { if (e.key == Crafty.keys.T) { this.alpha = 0.5; } if (e.key == Crafty.keys.O) { this.alpha = 1; } });
In the following demo, try moving the player over the food and then press the 'T' and 'O' keys. Pressing 'T' will set the opacity of the player to 0.5, and pressing 'O' will restore the opacity back to 1.
Now, let's bind a collision event to our player so that it grows in size whenever it hits food. We will have to add a collision component to the player and use the .checkHits()
method. This method will perform collision checks against all the entities that have at least one of the components that were specified when .checkHits()
was called.
When a collision occurs, a HitOn
event will be fired. It will also have some relevant information about the hit event. A HitOff
event is also fired once the collision ends.
playerBox.checkHits("Food") .bind("HitOn", function(hitData) { this.color("green"); this.w = this.w + 3; this.h = this.h + 3; });
The width and height of the player increases each time a hit occurs. We can use this event for a lot of things including changing the position of food or increasing the score in the game. You could also destroy the food entity by using the destroy()
method once the hit was detected.
Making a Selection
In the previous section, we had to just change the properties of a single entity. This could be done easily by using the variable assigned to each entity. This is not practical when we are dealing with about 20 different entities.
If you have used jQuery before, you might be familiar with the way it selects elements. For example, you can use $("p")
to select all the paragraphs. Similarly, you can select all entities that have the same common component by using Crafty("component")
.
Here are a few examples:
// Select all entities that have the Gravity component Crafty("Gravity"); // Select all entities that have either DOM or Canvas component Crafty("Gravity, Jumper"); // Select all entities that have both Gravity and Jumper component Crafty("Gravity Jumper");
Once you have the selection, you can get the number of elements selected using the length property. You can also iterate through each of these entities or bind events to all of them at once. The following code will change all food entities whose x
value is greater than 300 to purple.
Crafty("Food").each(function() { if(this.x > 300) { this.color("purple"); } });
Once you have a selection, you can use get()
to obtain an array of all entities in the selection. You can also access the entity at a specific index using get(index)
.
Final Thoughts
In this tutorial, you learned how to add or remove components from an entity. You also learned how to select entities with a given component or components and manipulate their attributes and properties one by one. All these things will be very useful when we want to manipulate different entities on our screen based on a variety of events.
If you have any questions about the tutorial, let me know in the comments.
Wednesday, February 15, 2017
Getting Started With Crafty: Introduction
If you have ever developed HTML5 games before, you might be familiar with a few game engines that can make game development a lot easier. They have all the code you need to detect collisions, spawn different entities, or add sound effects to your game. In this tutorial, you will learn about another such game engine called Crafty. It is very easy to use and supports all major browsers, including IE9.
Once minified, the library is only 127kb in size, so it won't result in any major delay in loading your game. It supports sprite maps, so you can easily draw game entities on the screen. You can also create custom events that can be triggered whenever you want and on whatever object you want.
There are also components for sounds, animation, and other effects. Overall, this game engine is a great choice if you want to develop some basic HTML5 games.
Initial Setup
The first thing that you need to do is include Crafty in your project. You can either download the file and load it in your projects or you can directly link to the minified version hosted on a CDN. Once the library has been loaded, you can use the following line to initialize Crafty:
Crafty.init([Number width, Number height, stage_elem]);
The first two arguments determine the width and height of our stage. The third argument is used to specify the element that we are going to use as our stage. If the third argument is not provided, Crafty will use a div
with id cr-stage
as its stage. Similarly, if the width and height arguments are missing, Crafty will set the width and height of our stage equal to the window width and height.
At this point, you should have the following code:
<html> <head></head> <body> <div id="crafty-game"></div> <script type="text/javascript" src="path/to/crafty.min.js"></script> <script> Crafty.init(600,400, document.getElementById('crafty-game')); </script> </body> </html>
Creating Entities
Entities are building blocks of a Crafty game. Everything from the player to the enemies and obstacles is represented using entities. You can pass a list of components to an entity. Each of these components will add extra functionality to our entity by assigning properties and methods from that component to the entity. You can also chain other methods to an entity to set various properties like its width, height, location, and color. Here is a very basic example of adding components to an entity:
Crafty.e('2D, Canvas, Color');
Every entity that you want to display to the user will need both the 2D component and a rendering layer. The rendering layer can be DOM
, Canvas
, or WebGL
. Please note that WebGL support was added in version 0.7.1. Currently, only the Sprite
, Image
, SpriteAnimation
, and Color
components support WebGL rendering. Text
entities still need to use DOM
or Canvas
for now.
Now, you can use the attr()
method to set the value of various properties including the width, height, and position of your entity. Most methods in Crafty will return the entity they are called on, and attr()
is no exception. This means that you will be able to chain more methods to set other properties of your elements. Here is an example:
Crafty.e("2D, Canvas, Color") .attr({x:200, y:100, w:200, h:50}) .color("orange");
This will create an orange rectangular entity on the stage.
Moving Entities Around
Now that you have created the entity, let's write some code to move it around using the keyboard. You can move an entity in four different directions, i.e. up, down, left, and right, by adding the Fourway
component to it.
The entity can then be moved by either using the arrow keys or W, A, S, and D. You can pass a number as an argument to the fourway
constructor to set the speed of the entity. Here is what the code of the entity should look like now:
Crafty.e("2D, Canvas, Color, Fourway") .attr({x:200, y:100, w:200, h:50}) .color("orange") .fourway(300);
You can restrict the movement of an entity to just two directions using the Twoway
component. Replacing the Fourway
component in the code above with Twoway
will confine the movement of the box to just the horizontal direction. This is evident from the following demo:
You can also add your own components to different entities for identification or to group similar entities together. In this case, I am adding the Floor
component to our orange box. You can use some other descriptive names to help you identify different entities.
Crafty.e("2D, Canvas, Color, Twoway, Floor") .attr({ x: 200, y: 340, w: 200, h: 50 }) .color("orange") .twoway(300);
There is another very useful component that you can use to move elements around, and it is called the Gravity
component. When added to an entity, it will make that entity fall down. You might want to stop the given entity from falling further, once it encounters some other entities. This can be achieved by passing an identifier component as an argument to the gravity function. Here is the code that makes the small black square fall on the floor or platform:
Crafty.e("2D, Canvas, Color, Gravity") .attr({ x: 200, y: 50, w: 50, h: 50 }) .color("black") .gravity("Floor");
Final Thoughts
As you see in the tutorial, we were able to create the basic structure of a simple game using very little code. All we had to do was add components to our entities and specify the values of different properties like width, height, or movement speed.
This tutorial was meant give you a basic idea of entities and other concepts related to Crafty. In the next part, you will learn about entities in much more detail. If you have any questions about this tutorial, let me know in the comments.
Tuesday, February 14, 2017
Check Out Our New Guide to Learning JavaScript
Do you want to learn JavaScript? We have plenty of JavaScript courses and tutorials here on Envato Tuts+, but sometimes it can be hard to know where to start.
So we've put together a free JavaScript learning guide to take you through the process from start to finish.
What You'll Learn
The learning guide starts right from the beginning, with the simple question "What is JavaScript?"
It then takes you through the fundamentals of JavaScript and introduces jQuery, a cross-platform JavaScript library that makes it easier to write code for the browser.
At each stage of the process, the guide contains links to courses and tutorials that go into much more depth, such as:
-
JavaScriptJavaScript FundamentalsDan Wellman
-
JavaScriptIntroduction to jQueryJeremy McPeak
-
JavaScriptJavaScript for Web DesignersAdi Purdila
-
ReactModern Web Apps With React and ReduxAndrew Burgess
The guide then introduces you to some popular JavaScript frameworks—both front-end ones like Angular 2 and back-end frameworks like Node.js.
Want to learn about cross-platform mobile development? There's a section on that too, with information on frameworks like Ionic 2, React Native, and Cordova.
And the guide ends with a roundup of more advanced Javascript resources.
Start Learning JavaScript Today!
We've designed this guide to help you learn JavaScript from the ground up. So whatever stage you're at right now, have a read of Learn JavaScript: The Complete Guide and check out the resources it links to. And if you find it helpful, be sure to bookmark it and use it as a reference throughout your learning journey.
JavaScript is constantly evolving, of course, so we'll keep updating the guide with new information and links to fresh resources. You can also sign up to our weekly digests to get a comprehensive summary of all our new code tutorials each week.
Monday, February 13, 2017
Sunday, February 12, 2017
Friday, February 10, 2017
Error Handling & Logging in Python
In software development, different types of errors can occur. They could be syntax errors, logical errors, or runtime errors.
Syntax errors most probably occur during the initial development phase and are a result of incorrect syntax. Syntax errors can be caught easily when the program is compiled for execution.
Logical errors, on the other hand, are a result of improper logical implementation. An example would be a program accessing a unsorted list assuming it to be sorted. Logical errors are the most difficult ones to track.
Runtime errors are the most interesting errors which occur, if we don't consider all the corner cases. An example would be trying to access a non-existent file.
In this tutorial, we'll learn how to handle errors in Python and how to log the errors for a better understanding of what went wrong inside the application.
Handling Exceptions in Python
Let's start with a simple program to add two numbers in Python. Our program takes in two parameters as input and prints the sum. Here is a Python program to add two numbers:
def addNumbers(a, b): print a + b addNumbers(5, 10)
Try running the above Python program, and you should have the sum printed.
15
While writing the above program, we didn't really consider the fact that anything can go wrong. What if one of the parameters passed is not a number?
addNumbers('', 10)
We haven't handled that case, hence our program would crash with the following error message:
Traceback (most recent call last): File "addNumber.py", line 4, in <module> addNumbers('', 10) File "addNumber.py", line 2, in addNumbers print a + b TypeError: cannot concatenate 'str' and 'int' objects
We can handle the above issue by checking if the parameters passed are integers. But that won't solve the issue. What if the code breaks down due to some other reason and causes the program to crash? Working with a program which crashes on being encountered with an error is not a good sight. Even if an unknown error is encountered, the code should be robust enough to handle the crash gracefully and let the user know that something is wrong.
Handling Exceptions Using Try and Except
In Python, we use the try
and except
statements to handle exceptions. Whenever the code breaks down, an exception is thrown without crashing the program. Let's modify the add number program to include the try
and except
statements.
def addNumbers(a, b): try: return a + b except Exception as e: return 'Error occurred : ' + str(e) print addNumbers('', 10)
Python would process all code inside the try
and except
statement. When it encounters an error, the control is passed to the except
block, skipping the code in between.
As seen in the above code, we have moved our code inside a try
and except
statement. Try running the program and it should throw an error message instead of crashing the program. The reason for the exception is also returned as an exception message.
The above method handles unexpected exceptions. Let's have a look at how to handle an expected exception. Assume that we are trying to read a particular file using our Python program, but the file doesn't exist. In this case, we'll handle the exception and let the user know that the file doesn't exist when it happens. Have a look at the file reading code:
try: try: with open('fname') as f: content = f.readlines() except IOError as e: print str(e) except Exception as e: print str(e)
In the above code, we have handled the file reading inside an IOError
exception handler. If the code breaks down due to unavailability of the file fname
, the error would be handled inside the IOError
handler. Similar to the IOError
exceptions, there are a lot more standard exceptions like Arithmetic
, OverflowError
, and ImportError
, to name a few.
Multiple Exceptions
We can handle multiple exceptions at a time by clubbing the standard exceptions as shown:
try: with open('fname') as f: content = f.readlines() printb except (IOError,NameError) as e: print str(e)
The above code would raise both the IOError
and NameError
exceptions when the program is executed.
finally
Clause
Assume that we are using certain resources in our Python program. During the execution of the program, it encountered an error and only got executed halfway. In this case, the resource would be unnecessarily held up. We can clean up such resources using the finally
clause. Take a look at the below code:
try: filePointer = open('fname','r') try: content = filePointer.readline() finally: filePointer.close() except IOError as e: print str(e)
If, during the execution of the above code, an exception is raised while reading the file, the filePointer
would be closed in the finally
block.
Logging in Python
When something goes wrong inside an application, it becomes easier to debug if we know the source of the error. When an exception is raised, we can log the required information to track down the issue. Python provides a simple and powerful logging library. Let's have a look at how to use logging in Python.
import logging # initialize the log settings logging.basicConfig(filename='app.log',level=logging.INFO) try: logging.info('Trying to open the file') filePointer = open('appFile','r') try: logging.info('Trying to read the file content') content = filePointer.readline() finally: filePointer.close() except IOError as e: logging.error('Error occurred ' + str(e))
As seen in the above code, first we need to import the logging Python library and then initialize the logger with the log file name and logging level. There are five logging levels: DEBUG, INFO, WARNING, ERROR, and CRITICAL. Here we have the set the logging level to INFO, hence the INFO and above logs would be logged.
Getting the Stack Trace
In the above code we had a single program file, hence it was easier to figure out where the error had occurred. But what do we do when multiple program files are involved? In such a case, getting the stack trace of the error helps in finding the source of the error. The stack trace of the exception can be logged as shown:
import logging # initialize the log settings logging.basicConfig(filename = 'app.log', level = logging.INFO) try: filePointer = open('appFile','r') try: content = filePointer.readline() finally: filePointer.close() except IOError as e: logging.exception(str(e))
If you try to run the above program, on raising an exception the following error would be logged in the log file:
ERROR:root:[Errno 2] No such file or directory: 'appFile' Traceback (most recent call last): File "readFile.py", line 7, in <module> filePointer = open('appFile','r') IOError: [Errno 2] No such file or directory: 'appFile'
Wrapping It Up
In this tutorial, we saw how to get started with handling errors in Python and using the logging module to log errors. We saw the usage of try
, except
and finally
statements, which are quite useful when dealing with error handling in Python. For more detailed information, I would recommend reading the official documentation on logging. Also have a look at the documentation for handling exceptions in Python.
Do let us know your thoughts in the comments below.
Thursday, February 9, 2017
Wednesday, February 8, 2017
Getting Started With Cassandra: Using CQL API and CQLSH
Apache Cassandra is one of the most popular open-source distributed database systems available. It was designed with the goal of handling large amounts of data stored in many servers distributed across geographies while providing high scalability and availability with no single point of failure. Cassandra systems can span multiple data centres, allowing low latency for all connected clients.
This is a three-part tutorial series where I will start with the basics of Cassandra, using CQLSH to create tables and records. Then I'll explain the various data types supported by Cassandra, and then we'll use a Go client library to handle Cassandra operations programmatically.
In this first part, I will cover how the Cassandra data model is laid out in brief and perform basic operations using CQLSH.
For this tutorial series, I am assuming that readers would be able to install Cassandra by themselves on their respective machines depending on the operating system.
The Cassandra Data Model
The Cassandra data model follows the column family approach, which can easily be understood as being analogous to a relational table structure but in a NoSQL way. The description below should make it clearer:
Keyspace
A keyspace can be seen as the outermost container for data in Cassandra. All data in Cassandra should live inside a keyspace. It can be seen as a database in RDBMS which is a collection of tables. In the case of Cassandra, a keyspace is a collection of column families.
Column Family
A column family can be seen as a collection of rows, and each row is a collection of columns. It is analogous to a table in RDBMS but has some differences. The column families are defined, but it is not necessary for each row to have all the columns, and columns can be added or removed from a row as and when required.
Column
The column is the basic unit of data in Cassandra. It has three values: key or column name, column value, and a timestamp.
Super Column
A super column is a special type of column which stores a map of other sub-columns. It makes storing complex data easier and also makes data fetching faster as each column family in Cassandra is stored in a single file on the file system.
Using Cassandra Console
CQLSH is the standard shell for interacting with Cassandra through CQL (Cassandra Query Language). CQL is very similar to SQL (which is mostly used for RDBMS) and hence makes it very easy for developers new to Cassandra to get working with it quickly. CQLSH is shipped with every Cassandra package and should already be installed on your machine when you installed Cassandra.
Create a Keyspace
As we saw in the data model described above, a keyspace
is the outermost container and should be created before anything else. To create it, run:
$ cqlsh localhost -e "CREATE KEYSPACE IF NOT EXISTS k1 WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'} AND durable_writes = true;"
In the above command, I have assumed that your Cassandra exists on localhost
without any user authentication. I have created a keyspace
called k1
with replication
and durable_writes
policy defined.
If you have user authentication defined, you can run:
$ cqlsh -u <username> -p <password> localhost -e "CREATE KEYSPACE IF NOT EXISTS k1 WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'} AND durable_writes = true;"
In the above command, replace <username>
and <password>
with your authentication credentials.
Running a command like this can be a bit cumbersome. Another way is to launch the CQLSH prompt and then run queries directly inside it.
$ cqlsh -u <username> -p <password> localhost Connected to Test Cluster at 127.0.0.1:9042. [cqlsh 5.0.1 | Cassandra 3.9 | CQL spec 3.4.2 | Native protocol v4] Use HELP for help. cassandra@cqlsh> CREATE KEYSPACE IF NOT EXISTS k1 WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'} AND durable_writes = true;
Moving ahead, I will be using the above method of running queries. Before running any other query, we need to tell CQLSH which keyspace should be used.
cassandra@cqlsh> USE k1; cassandra@cqlsh:k1>
The replication_factor
for a keyspace can be altered to suit how much replication is needed as per the replication class
.
cassandra@cqlsh:k1> ALTER KEYSPACE "k1" WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 3 };
Create and Alter a Table
A table is equivalent to a column family in Cassandra. Cassandra supports many different datatypes for storing data, which I will be covering in detail in the next part of this tutorial series. To create a table, simply run the CREATE TABLE
command.
cassandra@cqlsh:k1> CREATE TABLE person ( id text, name text, surname text, PRIMARY KEY (id));
To check how the structure of the table looks once created:
cassandra@cqlsh:k1> DESCRIBE person; CREATE TABLE k1.person ( id text PRIMARY KEY, name text, surname text ) WITH bloom_filter_fp_chance = 0.01 AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} AND comment = '' AND compaction = {'class': 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'} AND compression = {'chunk_length_in_kb': '64', 'class': 'org.apache.cassandra.io.compress.LZ4Compressor'} AND crc_check_chance = 1.0 AND dclocal_read_repair_chance = 0.1 AND default_time_to_live = 0 AND gc_grace_seconds = 864000 AND max_index_interval = 2048 AND memtable_flush_period_in_ms = 0 AND min_index_interval = 128 AND read_repair_chance = 0.0 AND speculative_retry = '99PERCENTILE';
Now let's say we want to alter the table to store the email of the person as well.
cassandra@cqlsh:k1> ALTER TABLE person ADD email text; cassandra@cqlsh:k1> DESCRIBE person; CREATE TABLE k1.person ( id text PRIMARY KEY, email text, name text, surname text ) WITH bloom_filter_fp_chance = 0.01 AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} AND comment = '' AND compaction = {'class': 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'} AND compression = {'chunk_length_in_kb': '64', 'class': 'org.apache.cassandra.io.compress.LZ4Compressor'} AND crc_check_chance = 1.0 AND dclocal_read_repair_chance = 0.1 AND default_time_to_live = 0 AND gc_grace_seconds = 864000 AND max_index_interval = 2048 AND memtable_flush_period_in_ms = 0 AND min_index_interval = 128 AND read_repair_chance = 0.0 AND speculative_retry = '99PERCENTILE';
Insert and Update Data
Inserting data into a Cassandra table using CQL is pretty straightforward.
cassandra@cqlsh:k1> SELECT * FROM person; id | email | name | surname ----+-------+------+--------- (0 rows) cassandra@cqlsh:k1> INSERT INTO person (id, name, surname, email) VALUES ('001', 'Shalabh', 'Aggarwal', 'contact@shalabhaggarwal.com'); cassandra@cqlsh:k1> SELECT * FROM person; id | email | name | surname -----+-----------------------------+---------+---------- 001 | contact@shalabhaggarwal.com | Shalabh | Aggarwal
In this table, we have all the fields for only one data type. Things become a bit complex when we're using different datatypes or composite datatypes. This will be a discussion in the next part of this series.
Let's say we want to update the value in the column email
to something else.
cassandra@cqlsh:k1> UPDATE person SET email='shalabh.agrwal@gmail.com' WHERE id='001'; cassandra@cqlsh:k1> SELECT * FROM person; id | email | name | surname -----+--------------------------+---------+---------- 001 | shalabh.agrwal@gmail.com | Shalabh | Aggarwal
Querying Data
Data in a table can be queried simply by using SELECT
statements.
Let's insert some more records and query them.
cassandra@cqlsh:k1> INSERT INTO person (id, name, surname, email) VALUES ('002', 'John', 'Doe', 'john@example.com'); cassandra@cqlsh:k1> INSERT INTO person (id, name, surname, email) VALUES ('003', 'Harry', 'Potter', 'harry@example.com'); cassandra@cqlsh:k1> SELECT * from person; id | email | name | surname -----+--------------------------+---------+---------- 002 | john@example.com | John | Doe 001 | shalabh.agrwal@gmail.com | Shalabh | Aggarwal 003 | harry@example.com | Harry | Potter (3 rows) cassandra@cqlsh:k1> SELECT name FROM person WHERE id='001'; name --------- Shalabh (1 rows) cassandra@cqlsh:k1> SELECT name FROM person WHERE id IN ('001', '002'); name --------- Shalabh John (2 rows)
More complex query operators like inequality operators can also be used, or several WHERE
conditions can be concatenated using AND
/OR
, etc.
Conclusion
Cassandra is one of the most popular NoSQL database systems available and is the best build to be used in distributed environments. Dealing with Cassandra is pretty easy for beginners with some knowledge of RDBMS and SQL.
CQL is very similar to SQL to a certain extent, and CQLSH makes testing and debugging much easier. In the next part of this series, I will cover the various datatypes provided by Cassandra and how to deal with them.
Tuesday, February 7, 2017
Monday, February 6, 2017
Sunday, February 5, 2017
Saturday, February 4, 2017
Friday, February 3, 2017
Thursday, February 2, 2017
6 Do's and Don’ts for a Great Android User Experience
The most popular Android apps have something in common: they all provide a great user experience. In this post, I'll share some tips that will help your app stand out.
Regardless of the kind of app you have in mind, or your target audience, designing a great user experience can help to ensure your app is a success. In this article, I’m going to share six things you should, and should not do, to make sure your app delivers the best possible experience to your end-users.
Since creating and launching an Android app is a multi-step process, I’ll be touching on every part of the Android development lifecycle—from making tough decisions about which versions of Android you should support, to creating a product that’ll appeal to a global audience, right through to analyzing your app’s post-launch performance.
1. Don’t Get Hung Up on Trying to Support Every Version of Android
While it’s natural to want to get your app in front of as many users as possible, don’t fall into the trap of assuming that supporting more versions of Android is always the best approach.
The key to attracting a large audience is to provide the best possible user experience, and setting your sights on supporting as many versions of Android as possible can actually damage the overall user experience.
The major issue is that as you continue to travel back through Android’s release history, it’s going to become harder to make your app play nicely with the earlier releases.
Sometimes, there may be a clear point where your app becomes incompatible with earlier releases of Android. For example, if your app absolutely requires access to Bluetooth Low Energy (BLE), then your app isn’t going to be able to run on anything earlier than Android 4.3—the version where BLE support was added to the Android platform.
However, sometimes this line may not be quite so clear-cut, and you may find yourself debating whether to modify or even remove non-critical features, in order to create something that can run on a particular version of Android. Small compromises can gradually chip away at the quality of the user experience, so always take stock of the impact these changes will have on your users.
In addition, customizing, optimizing and testing your app for each different version of Android requires time and effort, so you’ll also need to ask yourself whether this investment is worth the potential rewards. Basically, how many more users could you potentially gain by supporting each version of Android? You can get an indication of how many Android devices are running each release of the Android platform, by checking out the stats at Google's Dashboard.
Ultimately, there’s no universal right or wrong answer, so you’ll need to weigh up the pros and cons and decide what makes the most sense for your particular project.
Once you’ve decided which versions of Android you’re going to support, add this information to your module-level build.gradle file using minSdkVersion
(the lowest API your app is compatible with), targetSdkVersion
(the highest API level you’ve tested your app against), and compileSdkVersion
(the version of the Android SDK that Gradle should use to compile your app).
To make sure your app benefits from the latest Android features while remaining compatible with earlier releases, it’s recommended that you set your minSdkValue
as low as possible, while setting targetSdkVersion
and compileSdkVersion
to the latest version of the Android SDK.
2. Do Design for Multiple Screens
When you’re working on an Android app, it’s not uncommon to spend most of your time testing that app on your own Android smartphone or tablet. Particularly during the early stages of app development, creating multiple Android Virtual Devices (AVDs) will probably be the last thing on your mind.
However, don’t lose sight of the bigger picture! It’s easy to get hung up on designing for the one screen that’s physically in front of you, but it’s essential that your app looks good and functions correctly across a wide range of Android devices.
The Android system will automatically scale your layouts, drawables and other resources so they render at the appropriate size for the current screen, but for the best user experience you should aim to create the illusion that your app was designed for the user’s specific device. Relying on auto-scaling alone isn’t going to cut it!
To make sure your app delivers the best user experience across a wide range of devices, you’ll need to provide alternate resources that are optimized for different devices, such as drawables that target Android’s generalized density buckets, and alternate layouts that are optimized for landscape mode.
Once you’ve created your alternate resources, you’ll need to create alternate directories that are tagged with the appropriate configuration qualifiers, and then place the resources inside these directories—for example, a res/layout-land directory would contain layouts that are designed for landscape orientation. The Android system will then automatically load the resource that best matches the current screen configuration at runtime.
While most configuration qualifiers are relatively straightforward, providing resources that target different screen sizes is a bit more complex, requiring you to specify the exact dpi value where the system should start using this resource. So, you basically need to tell the system, “I want to use this layout when my app is being displayed on devices with 800dpi or more available screen width.”
You arrive at these values by testing your app across a wide range of different AVDs and making a note of all the screen sizes that your default resources are struggling with—for example, maybe your default layout starts to look cluttered once the device falls below a certain dpi threshold.
There are three screen size configuration qualifiers that you can use in your projects:
-
smallestWidth sw<value>dp. Allows you to specify the minimum horizontal space that must be available before the system can use the resources in this directory. For example, if you had a set of layouts that require 800dpi or more, you’d create a res/layout-sw800dp directory. Note that a device’s
smallestWidth
is a fixed value that doesn’t change when the user switches their device between portrait and landscape orientation. -
Available screen width w<value>dp. The minimum horizontal space that must be available before the system can use these resources. A device’s
w<value>dp
value does change when the user switches between portrait and landscape mode. -
Available screen height: h<value>dp. The minimum height that must be available before the system can use these resources. The device’s
h<value>dp
value will change depending on whether the user is holding their device in landscape or portrait.
Designing for multiple screens is mostly about creating alternate versions of your project’s resources and adding them to the appropriate directories—then rinse and repeat. However, there are some additional tricks you can use when creating these alternate resources that can really help create the illusion that your app was designed for the user’s specific device:
-
Use density-specific drawables in combination with 9-patch images. If the system needs to resize an image to fit the current screen then by default it’ll resize the whole image, which can lead to blurry, pixelated or otherwise odd-looking images. For the best possible results, you should specify the exact pixels that the system should replicate if it needs to resize your image, by delivering your project’s drawables as 9-patch images. Provide multiple 9-patch versions of each drawable, where each 9-patch targets a different screen density, and the system will then load the 9-patch image that’s the best fit for the current screen density and stretch the 9-patch image’s ‘stretchable’ sections, if required. You can create 9-patch images using any PNG editor, or you can use the Draw 9-patch editor that’s included in the Android SDK (you’ll find it in sdk/tools/Draw9patch.bat).
-
Create multiple dimens.xml files. It’s recommended that you define your layout’s values in a separate dimens.xml file rather than hard-coding them into your project. However, you can take this one step further and create multiple dimens.xml files that target different screen sizes and densities. For example, you might create a values-ldpi/dimens.xml file where you define the values that your app should use when it’s installed on a device that falls into the ‘low’ density category. The system will then load the appropriate dimensions for the current device, and apply them to your layout.
-
Consider using fragments. Fragments provide you with a way of dividing a single
Activity
into separate components that you can then display in different ways, depending on the current screen configuration. For example, you may choose to display multiple fragments side-by-side in a multi-pane layout when your app is installed on a device with a larger screen, and as separate Activities when space is more limited. The simplest way to add a fragment to your layout is by inserting a<fragment>
element into your layout resource file. Alternatively, you can add fragments to a layout via your application code—this method may be more complicated, but it gives you the added flexibility of being able to add, remove or replace fragments at runtime.
3. Do Consider Supporting Different Languages
Android is a global operating system, so if your app is going to provide the best user experience to this global audience then you should consider localizing your app for different languages, and potentially different regions.
Typically, the biggest part of localizing an app is translating your project’s strings.xml file into the different language(s) you want to support. Unless you happen to be fluent in your target languages, you’re going to need to enlist the help of a translator. If you don’t have anyone in mind, then the Developer Console’s Google Play App Translation services can point you in the direction of potential translators.
Once you’ve chosen a translator, you should take a critical look at your strings.xml file before sending it off for translation. Check for things like spelling mistakes and typos, and make sure your strings.xml is formatted in a way that makes it easy to read, bearing in mind that your translator may not be an Android developer themselves.
You should also provide as much context as possible, so make sure you add a comment to each string explaining what this string is for, where and when it’ll appear in your app, and any restrictions the translator needs to be aware of. For example, if a string needs to remain under 10 characters long in order to fit its allocated space in your layout, then this is something the translator needs to be aware of!
Once the translator has returned your translated strings.xml file(s), you’ll need to create a directory for each alternative file, which means you’ll need to work out what configuration qualifiers to use.
A locale configuration qualifier consists of an ISO code, which is essentially a language code, and an optional country or regional code, which is preceded by a lowercase r. For example, if you wanted to provide French (fr
) text for people located in Canada (can
), then you’d create a res/values-fr-rcan directory.
If you do provide localized text, then bear in mind that some strings may expand or shrink significantly during translation, so you’ll need to test that your layouts can accommodate all versions of your project’s strings.
The easiest way to test your localized resources is to install your app on an AVD and then emulate the different locations and language settings. At this point, the system will load the localized versions of your resources and display them in your app.
You can change the locale settings in a running AVD by issuing the following Android Debug Bridge (adb) commands:
adb shell
Followed by:
setprop persist.sys.locale fr-CAN;stop;sleep 5;start
Note, you’ll need to replace the fr-CAN
with whatever configuration qualifier you want to test against.
If you’ve designed your app with best practices in mind, then your layouts should be flexible enough to display the majority of your localized strings. However, if the length of your strings varies dramatically then you may need to provide alternate layouts that are optimized for different locales.
While your project’s strings.xml file is generally the main resource you’ll need to localize, you should also consider whether there are other resources you might need to translate such as drawables that contain text, video or audio that contains dialogue, or any resources that might be inappropriate for the locale you’re targeting.
Once you’re confident that you’ve provided all the necessary localized resources and you’ve performed your own round of testing, you should consider arranging a beta test with native speakers in each of your target locales. Native speakers can often pick up on mistakes that even a translator may overlook, and may be able to give you some advice about how you can make your app more appealing to this particular section of your audience. You can arrange this kind of targeted beta testing via the Google Play Developer Console.
When you’re finally ready to launch your app, make sure you take the time to create localized versions of your app’s Google Play page, as this will instantly make your app more attractive to international users who are browsing the Google Play store. You should also try and provide screenshots that clearly show the localized text inside your app, so users aren’t left wondering whether you’ve just translated your app’s Google Play page, and not the actual text inside your app.
And the hard work doesn’t end once you’ve launched your app! Once you've attracted your international audience, you’ll need to hang onto them by providing ongoing support across multiple languages—even if that means resorting to machine translators like Google Translate. At the very least, you should keep an eye on your Google Play reviews, to see whether users in certain locales are reporting similar issues, which may indicate an issue with one or more of your app’s localized resources.
4. Don’t Forget About Accessibility!
As an app developer, you’ll want to make sure that everyone can enjoy using your app, so it’s important to consider how accessible your app is to people who may be experiencing it without sound, colour or other visuals, or anyone who interacts with their Android device via an accessibility tool such as a screen reader.
Android is designed with accessibility in mind, so it has a number of built-in accessibility features that you can utilize without having to make any fundamental changes to your application’s code.
Let's look at a number of minor tweaks you can make to your project, which will have a huge impact on your app’s accessibility:
Consider Supplying Additional Content Descriptions
Accessibility services such as TalkBack read onscreen text out loud, making them an important tool for helping users with vision-related problems interact with their Android devices.
When designing your Android app, you should consider how easy it'll be for users to navigate your app using its onscreen text alone. If you do need to provide some additional context, then you can add a content description to any of your app’s UI components, which will then be read aloud by services such as TalkBack. To add a content description, open your project’s layout resource file and add an android:contentDescription
attribute to the UI component in question, followed by the description you want to use.
Support Focus Navigation
Users with limited vision or limited manual dexterity may find it easier to interact with their device using a directional controller, such as a trackpad, D-pad or keyboard, or software that emulates a directional controller. To make sure your app supports this kind of focus navigation, you’ll need to add the android:focusable="true”
attribute to each of your app’s navigational components.
When users navigate your app using directional controls, focus gets passed from one UI element to another in an order that's determined automatically using an algorithm. However, you can override these defaults and specify which UI component should gain focus when the user moves in a particular direction, by adding the following XML attributes to any of your UI components: android:nextFocusUp, android:nextFocusDown, android:nextFocusLeft, and android:nextFocusRight. For example:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://ift.tt/nIICcg" xmlns:tools="http://ift.tt/LrGmb4" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.jessicathornsby.myapplication.MainActivity"> <TextView android:layout_width="wrap_content" android:layout_height="74dp" android:text="Hello World!" android:id="@+id/textView" android:focusable="true" android:nextFocusDown="@+id/checkBox" /> <CheckBox android:text="CheckBox" android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/checkBox" android:layout_weight="1" android:focusable="true" android:nextFocusUp="@+id/textView" /> </LinearLayout>
Make Your Text Resizable
Users with vision impairments may choose to increase the size of the font that appears across their device. To make sure any font changes are reflected in your app, define your text in scale pixels—and don’t forget to test what impact Android’s various font sizes will have on your app’s UI, just in case you need to make some adjustments to your layout.
Use Recommended Touch Target Sizes
To help people with manual dexterity challenges navigate your app, it’s recommended that you set all touch targets to 48 x 48 dpi or higher, and make sure the space between these targets is at least 8 dpi.
Consider Disabling Timed-Out Controls
Some UI components may disappear automatically after a certain period of time has elapsed—for example, video playback controls often disappear once a video has been playing for a few moments.
The problem is that accessibility services such as Talkback don’t read controls until the user has focused on them, so if a timed-out control vanishes before the user has a chance to focus on it, then they won’t be aware that these controls even exist. For this reason, you should consider upgrading timed-out controls to permanent controls whenever accessibility services are enabled.
5. Do Put Your App’s Performance to the Test
Just because your app doesn’t crash or throw any errors during testing, doesn’t automatically mean it’s performing well, as some performance problems can be insidious and difficult to spot during regular testing. No-one enjoys using an app that takes forever to load, lags whenever you try and interact with it, and gobbles up the available memory, so you should always put your app through a number of performance-based tests before releasing it into the wild.
The Android SDK comes with a wide range of tools that you can use to specifically test your app’s performance. In this section, we’re going to look at a few of the ones you’ll definitely want to use; however, there are many more that are worth investigating (you’ll find more information in the official Android docs).
Note that all of these tools can only communicate with a running app, so you’ll need to make sure the app you want to test is installed on either an AVD or a physical device that’s connected to your development machine.
Before we get started, it’s worth noting that if you do identify a problem with your app’s performance, it’s recommended that you time your code before trying to fix this problem. You can then time your code again after you believe you’ve fixed the problem, and you'll be able to see exactly what impact your changes have had on your app’s performance.
You can time your code using TraceView, which you access by selecting the Android Device Monitor’s DDMS tab, then selecting the device and the process you want to profile, and giving the Start Method Profiling icon a click (where the cursor is positioned in the following screenshot).
At this point you can select either Trace based profiling (traces the entry and exit of every method) or Sample based profiling (collects the call stacks at a frequency specified by you). Once you’ve made your selection, spend some time interacting with your app. When you’re ready to see the results, you can load the trace file into the viewer by giving the Stop Method Profiling icon a click. The trace file displays each thread’s execution as a separate row, so you can see exactly how long each part of your project takes to run.
Identifying Overdraw
When the system draws your app’s UI, it starts with the highest-level container and then works its way through your view hierarchy, potentially drawing views on top of one another in a process known as overdraw. While a certain amount of overdraw is inevitable, you can reduce the time it takes your app to render by identifying and removing any instances of excessive or unnecessary overdraw.
If you have a device running Android 4.2 or higher, you can check the amount of overdraw present in any app installed on that device, by selecting Settings > Developer Options > Debug GPU Overdraw > Select overdraw areas. The system will then add a coloured overlay to each area of the screen, indicating the number of times each pixel has been drawn:
-
No Color. This pixel was painted once.
-
Blue. An overdraw of 1x. These pixels were painted twice.
-
Green. An overdraw of 2x.
-
Light red. An overdraw of 3x.
-
Dark red. An overdraw of 4x or more.
Most apps include some level of overdraw, but if you spot large areas of overdraw in your app then you should look at whether there’s any way of reducing the number of times each pixel is being redrawn, and one of the effective methods is removing unnecessary views.
The Android Device Monitor’s Hierarchy Viewer provides a high-level overview of your app’s entire view hierarchy, which can help you identify views that aren’t contributing anything to the final rendered image the user sees onscreen.
To launch Hierarchy Viewer, click the Android Device Monitor’s Hierarchy View button, and then select the device and the Activity you want to inspect, followed by the blue Load the view hierarchy into the tree view icon.
You may also want to export the Hierarchy Viewer output as a Photoshop document. This is a particularly effective technique for identifying views that aren’t contributing anything to the final UI, as each view is displayed as a separate Photoshop layer, meaning you can hide and reveal each layer and see exactly what impact this will have on the final image the user sees onscreen.
To create a PSD document, simply give the Capture the window layers as a Photoshop document icon a click.
Spotting Memory Leaks
Garbage collection (GC) is a normal system behaviour that’s important for ensuring your app and the device as a whole continue to run smoothly.
However, if your app isn’t managing memory properly—maybe it’s leaking memory or allocating a large number of objects in a short period of time—then this can trigger more frequent GC events that also run for longer. You can take a look at exactly what GC events are occurring in your app in the main Android Studio window; open the Android Monitor tab towards the bottom of the window, followed by the Monitors tab. The Memory Monitor tool will then start recording your app’s memory usage automatically.
If you keep interacting with your app, then eventually you’ll see a sudden drop in the amount of allocated memory, indicating that a GC event has occurred. Repeat this process, making sure you explore different areas of your app, and see what impact this has on the GC events. If you do spot any strange GC behaviour, then you’ll want to investigate further as this may indicate a problem with the way your app is using memory.
There are a couple of tools you can use to gather more information about your app’s memory usage. Firstly, you can use the Android Device Monitor’s Heap tab to see how much heap memory each process is using, which will flag up any processes that are gobbling up available memory.
To use the Heap tool, select the Android Device Monitor’s DDMS tab, followed by the process you want to examine, and then click the Update heap button. The Heap tab won’t display any data until after a GC event has occurred, but if you’re feeling impatient you can always trigger a GC event by giving the Cause GC button a click.
Another tool that can help you gather information about your app’s memory usage is Allocation Tracker, which lets you see exactly what objects your app is allocating to memory. To use Allocation Tracker, select the Android Device Monitor’s DDMS tab, followed by Allocation Tracker and the process you want to examine.
Click the Start Tracking button and spend some time interacting with your app, especially any parts that you suspect may be causing your app’s memory management problems. To see all the data Allocation Tracker has gathered during the sampling period, select the Start Tracking button, followed by the Get Allocations button.
6. Do Use Analytics Tools
Understanding your audience is a crucial part of creating a successful app.
Having access to data about who your audience is and how they’re using your app means you can make more informed decisions about how to build on your app’s success—and improve on the areas where your app isn’t doing quite so well.
Gathering this kind of user data is particularly useful for enabling you to identify trends across different sections of your audience. For example, you might decide that a certain segment of your audience is particularly valuable—maybe they’re racking up the largest percentage of in-app purchases, or they’re investing an above-average amount of time in your app. Armed with this information, you can take extra steps to support these users, making sure that your most valuable users remain engaged with your app.
At the other end of the scale, you might discover an area where your app is struggling. For example, maybe users who are running a particular version of Android have considerably lower engagement rates, or are more likely to uninstall your app. In this scenario, you might want to test your app on this particular version of Android to see whether there’s a bug or any other error that might be ruining the user experience for this section of your user base.
Basically, the more data you can gather about your audience and their behaviour, the greater your chances of keeping your existing user happy, attracting new users, and generally delivering an all-round great experience for everyone who comes into contact with your app.
In this section I’m going to look at two services that can put a wealth of information at your fingertips: Firebase Analytics and the Google Play Developer Console.
Firebase
You can use Firebase Analytics to gather data about your users, such as their age, gender and location, plus information on over 500 in-app events. You can even define your own custom events, if required.
To add Firebase Analytics to your project, you’ll need Google Play services 10.0.1 or higher and the Google Repository version 26 or higher, so open the SDK Manager and make sure these components are up to date. You’ll also need to be running Android Studio 1.5 or higher, and be signed up for a free Firebase account.
If you're running Android 2.2 or later, then you can connect your app to Firebase Analytics using the Firebase Assistant. Open Android Studio, launch the project in question, and:
-
Select Tools > Firebase from the Android Studio toolbar.
-
Click to expand the Analytics section, then select the Log an Analytics event link.
-
Click the Connect to Firebase button.
-
In the dialogue that appears, opt to create a new Firebase project.
-
Click the Connect to Firebase button.
-
After a few moments, you should see a Connected message.
-
Give the Add analytics to your app button a click.
-
In the subsequent dialogue, click Accept changes.
And that’s it! You can now view all your Firebase Analytics data by logging into the Firebase Console, selecting the project you want to examine, and then selecting Analytics. This data will update periodically throughout the day.
The Developer Console
You can also gain a valuable insight into your app’s performance and user behaviour via the Developer Console.
To view this data, log into your Developer Console account, select the app you want to examine, and then select Dashboard from the left-hand menu.
The Developer Console contains lots of useful information, so it’s well worth taking the time to explore its various sections in detail. However, there are a few areas that may be of particular interest:
-
User Acquisition Performance. This section contains a breakdown of how users are finding your app’s Google Play listing, for example the percentage of users who landed on your page via a UTM-tagged link, an AdWords ad, or the number of people who simply found your app by browsing the Google Play store.
-
Finance. If you’ve implemented a monetization strategy, such as in-app products or a subscription option, then you can use the Developer Console to review your app’s financial performance. The Finance section contains information such as the average amount each paying user is investing in your app, how much revenue is being generated by each product you offer through your app, and how much each section of your user base is spending, based on factors such as their geographical location, age, and the version of Android they’re using.
-
Crashes & ANRs. This section contains all the data users have submitted about application crashes and application not responding (ANR) errors, giving you a chance to identify and fix any issues that may be occurring in your app before users start leaving you negative reviews on Google Play. Note that crashes that aren’t reported by your users won’t appear in the Developer Console.
You may also want to consider downloading the Google Play Developer Console app, which lets you review all this information on the go.
Conclusion
In this article, we looked at six things you should and shouldn’t do when you’re developing an Android app. What are your golden rules for designing a great user experience? Leave a comment below and let us know.
In the meantime, check out some of our other courses and tutorials on Android programming!
-
Android SDKHow to Dissect an Android ApplicationAshraff Hathibelagal
-
Android SDKCreate an Android Cardboard 360 Video ViewerPaul Trebilcox-Ruiz
-
Android SDKJava vs. Kotlin: Should You Be Using Kotlin for Android Development?Jessica Thornsby