📜 ⬆️ ⬇️

Mobile Web Development: HTML5 Android App

Introduction



Fortunately, there is more than one way to write an application for a mobile phone. You can make a website, pack it in a special way, and voila, here is your application! It is this approach that the phonegap.com project offers us 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 website! It's clear! This is the same HTML, this is the same browser, the same Javascript. Finding a developer is not as difficult as, say, “native”. And if you multiply by the cross-platform nature of this solution, it may even seem like a panacea. Of course, we all know that no “pill” exists, but in a number of cases this is indeed the best practic
')
So, my work assignment sounded like this: Develop a client application for Android OS. The application is a game. Quest. The essence of the game is as follows: a group of people who want to have a rest, are divided into teams. Each team is given on a smartphone. In the smartphone app. Open the application. The application connects to the server and questions come from there. For each team they have their own. Questions may look like ordinary questions with answer choices, well, let's say, How old is the city of St. Petersburg ?, or location questions. Find the main entrance to the engineering castle. The team moves, finds the input, clicks We are in place and the coordinates go to the server. From the server, the answer is true or not. There are also photography issues. For example Take a photo of yourself against the background of an engineering castle. In sum, all the answers are evaluated and in the end one of the teams wins, gaining more points. In short everything.

Step 1 - Prototype


In general, the task is clear to us. Assume that the technical task has already been compiled. What else? Looking for prototypes. Here they are:

Unfortunately, the author of the image has been identified.

Step 2 - layouts


Next step. You need to draw them from. We take up the job, it turns out the following.

Unfortunately, the author of the image has been identified.

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 made like ExtJS. A large number of classes. We compose them, set them up and get the application. There is access to HTML elements, but at the framework level it is extremely unwise to manage elements. Roughly speaking, changing the standard visual display of elements is extremely difficult. But the data from the server to receive in JSON format is a pleasure.

And vice versa. Jquerymobile is access to elements, in fact extended Jquery. Tags are added to the elements. After loading, the framework for these tags adds elements to styles and other elements. I just failed to make friends with the JSON data from the server. Jquerymobile is waiting for html code from the server. Of course, you can get JSON and convert it on the client side to html code, which actually makes Sencha. But this is not a good practice. This goes against the framework ideology. There is a huge number of problems that are extremely difficult to solve.

Stop. And why do we need a framework? What is first, what is second, in essence, 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 the native application. Do we need this? Not. What about PhoneGap? And that he does not care what you use. No where no restrictions. Well then, let's just lay out the application, like a normal website and be done with it!

Step 4 - typeset


The layout process itself is no different from the standard one. There are certainly nuances, so let's talk about them. The first such nuance is the meta tags.

<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" /> 


Without this line in the header of the html code, your application will be displayed as a regular site. The browser will zoom in on it, which doesn't add any realism to the application.

Unlike the desktop browser, the mobile phone browser (probably not all) adds a frame to the elements that are in focus. A similar frame, when moving the 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 latest nuance is position: fixed. And this is really a problem, because there are no universal solutions. Everything depends on the mobile browsers themselves, they simply do not support, or support, but not completely, such functionality. It turns out to 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 fixed elements, which in general did not give realism and sometimes looked “no ice”.

Here is the link for a description of mobile browsers that support position: fixed
bradfrostweb.com/blog/mobile/fixed-position
There are also links to the Javascript libraries that emulate the work position: fixed and scrolling process. Unfortunately, the work of none of them cannot be called satisfactory.

In my particular case, the mobile platform was listed as Android 2.3, and it supports position: fixed, but the user zoom will not work, which is essentially nothing in the application. Specify in the viewport header

 <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" /> 


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, it is difficult to type out and look in the browser, in the monitor window. Resolution android application, say 320x480, and what is the screen size of your monitor? Emulators come to the rescue. The easiest emulator is already in your browser! If you upload pages to 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 User Agent and tick Device Metric. At the first stage it will be enough.

Unfortunately, the author of the image has been identified.

And there is an emulator from PhoneGap itself! emulate.phonegap.com
Called Ripple. It is put in the form of additions to Google Chrome. Hooray! Our capabilities 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, once we talked about emulators, you can’t say anything about an emulator, which should be 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 is already behaving quite like a real device. All the errors that were found on this emulator, all the same way were found on the device. And of course you need to say that using this emulator is quickly difficult. Long loading, hard to type text, etc. It is suitable for the very last stage. When your application is already working fine on all other emulators previously listed.

Step 6 - we program


Although the article is for programmers, to mix all the code here is just silly. I will describe in general. Programming a web application, in fact, is not different from programming a small site. There are the same methods and approaches, but performed on Javascript. The same MVC, the same patterns: singleton, compiler, 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 need to write App.Run ('SomeSome', '<parameters>')

Here is an example 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 example of a model.

 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, it’s the same as if the site were written in PHP. With the exception of the fundamental principle, Javascript is an asynchronous language and without a callback, neither here (unless you use special libraries of course)

Separately, I would like to stop at the nuances, namely, work with a smartphone camera. Out of the box, javascript can't do this. Cordova library comes to the rescue, which PhoneGap offers to connect. But the link to the description of the work with the camera phone

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 deal with was that after taking a photo, the camera simply showed a black screen and did not return to the application. As it turned out, this is due to the fact that by default the photo was taken of maximum quality and the file was large. The process of its transfer to the application, due to the low power of the phone itself, takes considerable time. I had to make changes to the demo code

 navigator.camera.getPicture(OnSuccess, OnFail, { quality: 75, allowEdit: true, targetWidth: 280, targetHeight: 280, destinationType: destinationType.DATA_URL }); 


But this was not all. The getPicture method returns the base64 encoded image, but the data between the server and the client are transmitted as JSONP requests.
Obviously, it is impossible to transfer such amount of data via GET request. The server part, by the way, I do not remember whether I said whether or not, in PHP. Yes, not the best solution, you can forget about WebSocket. Proxying is also not done. Probably, the solution to this problem was one of the most difficult. And the solution was the following. Time goes and standard classes are expanded, 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 response handler from the server is “hung up” on it, and in the Content-Type header you specify application / x-form-urlencoded, then the browser will make a cross-domain request using the POST method, which we actually 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. The cross-domain query, no matter how it is implemented, is synchronous, even though the above code looks like asynchronous.

I also faced the Same Origin Policy problem. The solution to this problem lies on the server side. In the configuration files, permission is set for the cross-domain query and deal with the end.

I also tried 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 don’t need advanced phone functions: accelerometer, compass, camera, media, etc. it is not necessary to connect the cordova library (and this is about 300 kilobytes). Geolocation, by the way, is available without it.

Step 7 - debug


Here is our application ready. It is done and works great on the Ripple emulator (see the section on emulators). The most interesting begins, namely, debugging on the phone. But first, let's try to run the application on the emulator, in eclipse. Before each launch of the application on the emulator, the system asks to clean the project. Project -> Clean. Do not forget to do it. Push Run - let's go!

After loading the emulator, there will be a huge number of messages in the LogCat Eclipse panel. The first question that arises is which ones are ours? In order to see only your errors, and in particular, to see the messages that the application displays in console.log, you need to configure the filter. In the LogCat panel, on the left, there is a separate unit, Saved Filters. Opening it, you will of course see an empty list, for we do not yet have filters. Click on the plus sign and see the window

Unfortunately, the author of the image has been identified.

Enter in the Log Tag web console, as in the picture and now the Log console will display 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 previously.

Unfortunately, the author of the image has been identified.

 JSCallback Error: Request failed with status 0 at :1180915830 


Begin to study the error. It is obvious that the error is caused at the time of receiving data from the server. Error says that status 0 is coming. We are starting to search for a solution on Google, and this 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 are looking for where the 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 if (xmlhttp.status === 200) to replace if (xmlhttp.status === 200) with if (xmlhttp.status === 200 || xmlhttp.status === 0) and voila - no effect!

I will not continue to tell you 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 still crashed 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 Hesh, led to such an error, remains a mystery to me. If anyone has any thoughts on this subject - well.

Step 8 - Run


In order to launch the application directly on the phone, it’s enough to enter the settings, select the Development section and check the box next to USB Debugging. Next, 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 already on the device.

Source: https://habr.com/ru/post/183458/


All Articles