
Mobile Web Development: HTML5 Android App
- Tutorial
- Recovery mode
Introduction
Fortunately, there is more than one way to write a mobile application. You can make a site, pack it in a special way, and voila, here you have the application! It is this approach that the phonegap.com project offers us precisely about this method and will be discussed in this article.
I am sure that it is not worth discussing the economic feasibility of this approach. She is on the face. Yes, you need more knowledge than the average web developer, but still, this is a site! This is clear! This is the same HTML, this is the same browser, the same Javascript. It’s not as difficult to find a developer as we say “native”. And if you multiply by the cross-platform nature of this solution, in general it might seem that this is a panacea. Of course, we all know that no “pill” exists, but in some cases, it’s really the best practic
So, my work task was as follows: Develop a client application for Android. The application is a game. Quest. The essence of the game is as follows: a group of people who want to relax interestingly are divided into teams. Each team is given a smartphone. In the smartphone application. Open the application. The application connects to the server and questions come from there. For each team they are different. Questions may look like ordinary questions with answer options, well, say, How old is the city of St. Petersburg ?, and location questions. Find the main entrance to the engineering castle. The team moves, finds the entrance, clicks We are in place and the coordinates go to the server. The answer from the server is true or not. There are also questions of photography. For example, Take a picture of yourself against the backdrop of an engineering castle. In total, all answers are evaluated and as a result one of the teams wins, gaining more points. In short, everything.
Step 1 - Prototypes
In general, the task is clear to us. Suppose that the terms of reference have already been drawn up. What else? We need prototypes. Here they are:
Step 2 - Layouts
The next step. You need to draw them from. We get to work, it turns out the following.
Step 3 - choose a framework
In fact, there are two of them:
1. Sencha Touch
http://www.sencha.com/products/touch
2. Jquerymobile
http://jquerymobile.com/
Take Sencha Touch. The framework is similar to ExtJS. A large number of classes. We compose them, configure them - we get the application. There is access to HTML elements, but it’s extremely unwise to manage elements at the framework level. Roughly speaking, changing the standard visual display of elements is extremely difficult. But getting data from the server in JSON format is a pleasure.
And vice versa. Jquerymobile is access to elements, essentially enhanced by jquery. Tags are added to the elements. After loading, the framework for these tags complements the elements with styles and other elements. But I just didn’t manage to make friends with the JSON data from the server. Jquerymobile expects html code from the server. Of course, you can get JSON and convert it on the client side to html code, which Sencha actually does. But this is not good practice. This goes against the ideology of the framework. There are a lot of problems that are extremely difficult to solve.
Stop. Why do we need a framework? That the first, that the second, in fact, is, so to speak, a ready-made element base, ready-made solutions, the purpose of which is to help you make the application (website) visually similar to a native application. Do we need this? Not. But what about Phonegap? And what is he, he does not care what you use. No where no restrictions. Well then, let's just make up the application, like a regular site and deal with the end!
Step 4 - layout
The layout process itself is no different from the standard. There are certainly nuances, and let's talk about them. The first such nuance is meta tags.
Without this line in the header of the html code, your application will appear as a regular site. The browser will zoom it, which does not add any realism to the application.
Unlike the desktop browser, the mobile phone browser (probably not all) adds a frame to the elements on which the focus is set. A similar frame, when you focus, is by default in Google Chrome, at the moment when we enter data in the next field. It is treated similarly.
input:focus {
outline: 0 none;
}
textarea:focus {
outline: 0 none;
}
.Button:focus {
outline: 0 none;
}
And the most recent nuance is position: fixed. And this is really a problem, because there are no universal solutions. Everything rests on the mobile browsers themselves, they simply do not support, or support, but not fully, such functionality. Nor can I fix the control panel with one solution for all cases. For example, jquerymobile, up to version 1.1, in case the browser does not support position: fixed, emulated scrolling and dynamically changed the position of the fixed elements, which in general did not give realism and sometimes looked like “no ice”.
Here at this link there is a description of mobile browsers that support position: fixed
bradfrostweb.com/blog/mobile/fixed-position
and also there are links to Javascript libraries that emulate the work of position: fixed and the scrolling process. Unfortunately, the work of none of them can be called satisfactory.
In my particular case, the mobile platform was listed as Android 2.3, and it supports position: fixed, but the custom zoom will not work, which is essentially useless in the application. Specify in the viewport header
And prescribe styles
.Header {
background-color: white;
background-image: none;
border: none;
text-shadow: none;
border-bottom: white solid 3px;
font-weight: bold;
position: fixed;
width: 100%;
height: 62px;
top: 0;
left: 0;
z-index: 100
}
That's all.
Step 5 - Emulators
Obviously, layout and looking in the browser, in the monitor window, is difficult. Resolution android application, say 320x480, and what are the screen sizes of your monitor? Emulators come to the rescue. The simplest emulator is already in your browser! If you load the pages in Google Chrome and press Ctrl + Shift + I, the browser will show you the developer tools. In the lower right corner you can find the gear icon, click on it. Next, select the Override tab and here it is, your emulator. Select the User Agent and check the Device Metric checkbox. In the first stage, this will be enough.
And there is an emulator from PhoneGap itself! emulate.phonegap.com
Called Ripple. It is put in the form of additions to Google Chrome. Hurrah! Our opportunities have increased dramatically. If in your application you use the cordova library to expand the functionality of the application, say to work with a phone camera or a compass, then Ripple will give you the opportunity to simulate these processes.
Well, since there was talk about emulators, you can’t say about the emulator that is installed with Eclipse, if you follow the instructions from Phonegap
docs.phonegap.com/en/2.2.0/guide_getting-started_android_index.md.html#Getting%20Started% 20with% 20Android
This emulator already behaves quite like a real device. All errors that were found on this emulator, all were similarly found on the device. And of course, I must say that using this emulator is operationally difficult. It takes a long time to load, it is difficult to type text, etc. It is suitable for the very last stage. When your application is already working fine on all the other previously listed emulators.
Step 6 - Programming
Although the article is for programmers, it’s just silly to put all the code here. I will describe in general. Programming a web application is essentially no different from programming a small site. Here the same methods and approaches, but implemented in Javascript. The same MVC, the same patterns: singleton, linker, etc.
Here is the front controller
var App = {
Init: function() {
this.model = new Model(this.url);
this.view = new View();
this.controller = new Controller({
model: this.model,
view: this.view
});
return this;
},
Run: function(task, params) {
if (typeof task == 'undefined') {
this.controller.Login();
} else if (typeof this.controller[task] == 'undefined') {
this.controller.Login();
} else {
this.controller[task](params);
}
return this;
},
Done: function() {
return this;
}
}
$(document).ready(function() {
App.Init();
App.Run();
App.Done();
});
* There are no magic methods in javascript. If we say in PHP we can use __call, and call App.SomeSome ('<parameters>'), then we will need to write App.Run ('SomeSome', '<parameters>')
Here is an example of a controller:
var Controller = function(params) {
this.view = params.view;
this.model = params.model;
}
Controller.prototype = {
Login: function() {
this.view.Login();
},
LoginSubmit: function() {
var that = this,
value = this.model.GetLoginFormValue(),
errors = this.model.GetLoginFormErrors();
if (errors !== false) {
this.view.Login(value, errors);
} else {
this.model.SendToServer('teamLogin', value, function(err, data) {
if (!err) {
that.model.SetTeam(data);
that.model.ListenServer(data.lastMessageId);
that.Welcome();
} else {
that.view.ShowPopup('error', data)
}
});
}
},
Welcome: function() {
var that = this;
this.model.GetWelcomeContent(function(err, data) {
if (!err) {
that.view.Welcome(data);
} else {
that.view.ShowPopup('error', data);
}
});
}
Here is a small model example
var Model = function(url) {
this.url = url;
}
Model.prototype = {
GetHelpChat: function(callback) {
var url = 'helpChat?team='+this.team.teamId+'&hash='+this.team.hash;
this.ReciveFromServer(url, function(err, data) {
if (err) {
callback(true, data);
} else {
callback(false, data);
}
});
},
Here is an example view
var View = function() {
this.page = $('.Page');
}
View.prototype = {
TaskIndex: function(status, time, tasks) {
var num = Util.GetRndNumber();
this.Show(
Html.Header(
Html.IconPanel(status),
Html.TimePanel(time)
),
Html.Content(
Html.TaskPanel(tasks)
),
Html.Footer(
Html.ButtonPanelBottom('task')
)
);
setInterval(Timer.Total, 1000);
setInterval(Timer.Current, 1000);
Util.SetScrollToTop();
},
In fact, here is the same as if the site was written in PHP. With the exception of the fundamental principle, Javascript is an asynchronous language, and without a callback, it’s not how (unless you use special libraries of course) I
would like to separately dwell on the nuances, namely, working with a smartphone’s camera. Out of the box, javascript cannot do this. The Cordova library comes to the rescue, which PhoneGap offers to connect. And here is the link to the description of working with the phone’s camera
http://docs.phonegap.com/en/2.2.0/cordova_camera_camera.md.html#Camera
When working with advanced Javascript functions and in particular with the camera, I expected the most problems from them. And not in vain. The first thing I had to face was that after taking a photo, the camera simply showed a black screen and did not return back to the application. As it turned out, this is due to the fact that by default the photo was taken of the highest quality and the file turned out to be large. The process of transferring it to the application, due to the low power of the phone itself, takes considerable time. Had to make demo code changes
navigator.camera.getPicture(OnSuccess, OnFail, {
quality: 75,
allowEdit: true,
targetWidth: 280,
targetHeight: 280,
destinationType: destinationType.DATA_URL
});
But that was not all. The getPicture method returns a base64 encoded picture, but the data between the server and the client is transmitted as JSONP requests.
Obviously, it is impossible to transmit such a quantity of data through a GET request. The server side, by the way, I don’t remember saying whether or not I spoke in PHP. Yes, not the best solution, you can forget about WebSocket. Proxying is not done either. Probably the solution to this problem was one of the most difficult. And the solution was as follows. Time goes by and standard classes expand, new methods are added. So the XMLHttpRequest class has got new events. In addition to the standard onreadystatechange, the onload event also appeared. If the handler of the response from the server is “hung” on it, and in the Content-Type header specify application / x-form-urlencoded, then the browser will make a cross-domain request using the POST method, which is exactly what we need. Here is an example
var xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.onload = function(e) {
if (this.readyState == 4) {
if (this.status == 200) {
var r = JSON.parse(this.responseText);
if (r.success) {
callback(false, r.data);
} else {
callback(true, r.message);
}
} else {
that.view.ShowPopupWindow('Error', msg.ERROR_CONNECTION);
}
}
}
And yet, a very important point. A cross-domain request, no matter how it is implemented, is synchronous, even though the above code looks like asynchronous.
I also encountered the Same Origin Policy problem. The solution to this problem lies on the server side. In the configuration files, permission for cross-domain request and deal with the end are registered.
I also tried the FormData API
developer.mozilla.org/en-US/docs/Web/API/FormData?redirectlocale=en-US&redirectslug=Web%2FAPI%2FXMLHttpRequest%2FFormData
But, unfortunately, this API, the mobile phone browser does not support .
I would also like to note that in case you do not need advanced functions for working with the phone: accelerometer, compass, camera, media, etc. connecting the cordova library is optional (which is approximately 300 kilobytes). Geolocation, by the way, is available without it.
Step 7 - debugging
Here is our application ready. Designed and worked perfectly on the Ripple emulator (see the section on emulators). The fun part begins, namely debugging on the phone. But first, try to run the application on the emulator, in eclipse. Before each application launch on the emulator, the system asks to clean the project. Project -> Clean. Do not forget to do it. Click Run - let's go!
After loading the emulator, there will be a huge amount of messages in the LogCat Eclipse panel. The first question that arises is what are ours? In order to see only your errors, and in particular, to see the messages that the application displays in the console console.log, you need to configure the filter. In the LogCat panel, on the left, there is a separate block, Saved Filters. Opening it, you will certainly see an empty list, because we do not have filters yet. Click on the plus sign and see the window
Enter in the Log Tag web console, as in the picture, and now the Log console will show messages from your web application.
As expected, the emulator in the browser is far from being an emulator in Eclipse. Indeed, there were errors that were not there before.
JSCallback Error: Request failed with status 0 at :1180915830
We begin to study the error. Obviously, the error is caused when the data is received from the server. The error says that the status is 0. We start looking for a solution on Google, and here is what we find
simonmacdonald.blogspot.ru/2011/12/on-third-day-of-phonegapping-getting.html
stackoverflow.com/questions/11230685/phonegap- android-status-0-returned-from-webservice
We conclude: you probably need to add status 0 as the correct status to continue processing the server response. We look for where these JSCallback messages are and find it in the cordova.js file on line 3740 (cordova-2.1.0.js)
function startXhr() {
// cordova/exec depends on this module, so we can't require cordova/exec on the module level.
var exec = require('cordova/exec'),
xmlhttp = new XMLHttpRequest();
// Callback function when XMLHttpRequest is ready
xmlhttp.onreadystatechange=function(){
if (!xmlhttp) {
return;
}
if (xmlhttp.readyState === 4){
// If callback has JavaScript statement to execute
if (xmlhttp.status === 200) {
// Need to url decode the response
var msg = decodeURIComponent(xmlhttp.responseText);
setTimeout(function() {
try {
var t = eval(msg);
}
catch (e) {
// If we're getting an error here, seeing the message will help in debugging
console.log("JSCallback: Message from Server: " + msg);
console.log("JSCallback Error: "+e);
}
}, 1);
setTimeout(startXhr, 1);
}
// If callback ping (used to keep XHR request from timing out)
else if (xmlhttp.status === 404) {
setTimeout(startXhr, 10);
}
// 0 == Page is unloading.
// 400 == Bad request.
// 403 == invalid token.
// 503 == server stopped.
else {
console.log("JSCallback Error: Request failed with status " + xmlhttp.status);
exec.setNativeToJsBridgeMode(exec.nativeToJsModes.POLLING);
}
}
};
We are trying to replace it
if (xmlhttp.status === 200)
with if (xmlhttp.status === 200 || xmlhttp.status === 0)
voila - no effect! Further I will not tell how I spent the whole day circling around this error. I can only say that I was ready to despair, because nothing could help me. The application crashed anyway, until I just decided to comment out part of the code. And lo and behold! The error has disappeared! Returning, in parts, my code, I found part of it that led to an error.
var Util = {
SetNewHash: function(hash) {
/**
* Это не работает в Android 2.3!!
*/
//location.href = 'http://'+location.host+location.pathname+'#'+hash;
},
Why the change of the Hash led to such an error remains a mystery to me. If anyone has any thoughts on this matter - well.
Step 8 - Run
To launch the application directly on the phone, just go to the settings, select the Development section and check the box next to USB Debugging. Further, by pressing RUN in eclipse, the environment will determine that your phone is connected to USB, and I hope you have already done this, and will start running the application on the device.