Is cross-platform development on Titanium a terrible end or horror without an end?

Published on February 27, 2014

Is cross-platform development on Titanium a terrible end or horror without an end?

    Image and video hosting by TinyPicIn this topic, we want to share our experience in creating mobile applications on the platform for developing cross-platform applications Titanium. Around 2011, we started working with cross-platform frameworks. First it was PhoneGap, then Titanium. Have made a dozen applications that work to this day, both in Russia and in the USA. We consciously want to move away from evaluations - is it bad or good to develop cross-platform applications, and focus on the difficulties that you will encounter in terms of developing and maintaining these applications.

    In our opinion, the topic will be useful both to readers who are going to order the application so that they can make a choice between native development for each platform and cross-platform, and to developers who decide where to go.

    So, let's start with a list of problems that you will encounter.

    Problems:

    1. Double click problem.
    2. If code.
    3. Managing memory on Android.
    4. Insufficient implementation of individual functions, including standard ones.
    5. Javascript - lack of typing slows down the process of writing code and complicates maintenance.
    6. Lack of InterfaceBuilder - slows down the process of writing an application, the entire UI is written in code.
    7. The Titanium SDK is updated later than the operating system SDK.
    8. Each version of the SDK contains bug fixes and introduces new bugs.


    Examples we have encountered with these problems.

    Double click problem

    The mobile application contains various controls - buttons, text input fields, radio buttons, etc. When the user clicks on any of them, the application receives a signal about this in the form of an event - an object containing information about which element the action was performed with, what action it is, for example, long or short press, and more. In native applications, i.e. in applications written using standard development tools (iOS SDK for iPhone and Android SDK for Android), the corresponding control is blocked during event processing, and a new event cannot be received from it. Probably everyone noticed that if you click on the send message button in the standard application, then for a while the button turns gray and you can’t click on it a second time. Such blocking in native applications occurs automatically and does not require the programmer to write code or other actions. An application written using the Titanium SDK does not have such a lock, so each control can send several events of the same type. If, for example, a button opens a new application screen, then two or more screens may open.

    This is incorrect and inconvenient for the user. Apple, most likely, will not even miss such an application in the AppStore. You have to block the UI or use the flags in order to ignore subsequent events until the processing of the first one is completed. Our testers called this problem a double-click problem.

    We came up with several ways to solve this problem:

    1. In the first version, we made a screen lock: after the first press, the entire application screen closes with the message "Please wait ..." on a translucent gray background. For simple situations, the message is hidden after a certain time (about half a second). For longer operations that require, for example, downloading data, it is necessary to lock the screen for an indefinite period, and then at the end of the event processing, a special screen unlock function is called that hides the message. The message itself serves as a shield for subsequent user actions.
    It looked something like this: 2. In another version, a check is made that at least the specified time interval has passed since the last press. If half a second has passed since the last click, then the event is processed, otherwise it is ignored.

    Image and video hosting by TinyPic



    In both cases, this is less clear to the user than the behavior of the native application, in which the button simply cannot be pressed during the processing of the previous event.

    If code

    Many things for different operating systems are implemented using various functions, properties and modules, in the program code you constantly have to write “if it’s Android, then we do it, and if it’s iPhone, then like that.” First, a variable is started to write less code, its value is filled at the beginning of the application:

    if(Titanium.Platform.name.indexOf('iPhone') >= 0) {
      isIPhone = true;
      isAndroid = false;
    } 
    


    And you begin to insert checks everywhere.
    getting objects from the database:

    if(isAndroid) {
      fieldCount = resultSet.fieldCount;
    } else {
      fieldCount = resultSet.fieldCount();
    }
    


    This is a very wonderful example. Firstly, it shows how much attention you need to pay to the little things when you work with Titanium. Secondly, the documentation was initially written incorrectly, so the developers had to look at the source code of Titanium to fix the code of their application. In general, the Titanium documentation usually says for which operating system one or another parameter works: If Android is drawn under the parameter, then it works in Android applications, if the iPhone or iPad is drawn, then this parameter is for iOS. And there are a lot of such notes in the documentation. Sections in Tables

    Image and video hosting by TinyPic





    If you update the data in the table with sections on Android, the application will crash. Since sections on Android do not know how to behave as beautifully as on iOS, remaining at the top of the screen when scrolling the list under the section, we inserted the section view into the first list item for each section. Then the application does not crash.

    Displaying text - labels, and

    Android text input fields are indented inside text fields, and these indents depend on the version of Android, the size of the text field and the font size in it. A common problem is invisible text, which seems to be included in the text box, but not included, due to these internal indents. If you want everything to look equally good, you will have to write a method with a lot of ifs that will determine how the text should be displayed now.

    Hidden text
    var createLabel = function(properties, linesCount) {
      var fontSize = isNotEmpty(properties.font) && (properties.font.fontSize) ? properties.font.fontSize : defaultFontSize;
      var offset = Math.floor(fontSize/8);
      var heightOffset = 2 * offset;
      var lineHeight = fontSize + heightOffset;
      var androidTopOffset = 0;
      if (isAndroid) {
        androidTopOffset = (fontSize <= getControlSize(18)) ? Math.floor(fontSize / 4) : Math.floor(fontSize / 11);
      }
      if(isNotEmpty(properties.top)) {
        properties.top = properties.top - offset - androidTopOffset;
        if(isNotEmpty(properties.height) && properties.height != Ti.UI.SIZE) {
          properties.height = properties.height + heightOffset;
        } else if(isNotEmpty(linesCount)) {
          properties.height = lineHeight * linesCount;
        }
      }
      if (isEmpty(properties.font)) {
        properties.font = {};
      }
      properties.font.fontFamily = fontName;
      properties.font.fontWeight = 'normal';
      if (isEmpty(properties.wordWrap)) {
        if (isNotEmpty(linesCount) && linesCount == 1) {
          properties.wordWrap = false;
        } else {
          // properties.wordWrap = true;
        }
      }
      // DEBUG
      if (isBlank(properties.color)) {
        properties.color = '#ff0000';
        properties.font.fontWeight = 'bold';
        Ti.API.error('Error in createCommonLabel: not specified label color. Label text = ' + properties.text);
      }
      var label = Ti.UI.createLabel(properties);
      return label;
    };
    


    Special attention should be paid to the DEBUG section: if the text does not have a color specified, we set the color to red. This is done because the default color values ​​for iPhone and Android are different and you need to always color each label - if you want the application to look the same on different devices. It is also necessary to calculate the size of each element, because the screens of Androids are very different, and the font size and controls should correspond to the size of the screen. Due to such inconsistencies between the platforms, the application needs to be tested on all devices and operating systems, and even when something is fixed for one of the platforms, it is sometimes impossible to predict how the fix will affect other devices.

    Android memory management

    When most of the application was already written, we faced a very serious problem when testing on Android: when it is actively working in the application, it crashes after 10 minutes. It turned out that for each screen a large chunk of memory is allocated, which is not freed, even when the screen is closed. This problem has not been completely resolved, the latest version on which they checked was 3.1.1. To increase the stability of the application, it was necessary to reduce the appearance of the interface, remove all background images, reduce the number of elements, rewrite part of the standard controls.

    If the application requires downloading data from the Internet, then the problem may also be covered. If the application does not have enough memory, then the application will crash when loading data. In the native Android application, the developer can add exception handling, errors that occur during the operation of the application, you can even display a message about the lack of memory for the user (although it is preferable to solve the problem in some other way). When you write on Titanium, this is not possible. When loading data, an error occurs deep inside Titanium, and it cannot be handled by software.

    Inadequate implementation of individual functions, including standard ones

    Probably the largest number of such problems we have collected when realizing the ability to add photos in an Android application. The first problem we encountered was the inability to write code to exit the photographing mode. Those. if the application should show a photographing screen with a pair of “Take photo” and “Cancel” buttons, then by clicking on the cancel button you cannot cancel anything. The second problem is the inability to find out that the user has left the photographing mode by pressing the "Back" button on the phone. Those. if you need to update the interface when exiting the photographing mode, you cannot do this.

    Many such deficiencies and problems have been on the list of Titanium bugs for several versions.
    jira.appcelerator.org/browse/TIMOB-16182
    jira.appcelerator.org/browse/TIMOB-16199

    General list of problems:
    jira.appcelerator.org/secure/IssueNavigator.jspa ?

    Javascript - lack of typing, slows down the process of writing code and complicates maintenance

    Most modern mobile application developers are used to writing programs in high-level languages ​​such as Java and Objective-C, but you have to write Javascript in Titanium. This is more common for web developers, but they do not know the basics of mobile development, have not encountered memory restrictions and the rules for creating mobile application interfaces. Javascript is a dynamic typing language, which means that a variable is associated with a type when it is assigned a value, and not when a variable is declared. Thus, in different parts of the program the same variable can take on different types of values. For a programmer, this means that he must make sure that the program works with the data in accordance with their types, because otherwise the application will crash.

    Lack of InterfaceBuilder - slows down the process of writing an application - the entire UI is written in code

    Many mobile applications contain lists: twitter, news feed, letters in the mailbox, address book, reminders, search results - these are all lists. Typically, a combo screen is implemented on a table. Each element is a cell in which a template is described. For an iOS application, the programmer creates a template (or various templates for complex tables) in Interface Builder and writes code to display the data in this template:

    Image and video hosting by TinyPic



    - (void)updateViews
    {
        if (!self.package)
            return;
        static NSDateFormatter* df = nil;
        if (!df) {
            df = [[NSDateFormatter alloc] init];
            df.dateStyle = NSDateFormatterShortStyle;
            df.timeStyle = NSDateFormatterNoStyle;
        }
        self.labelCity.text = self.package.city;
        self.labelPackageId.text = self.package.formattedPackageId;
        self.labelItemsCount.text = @(self.package.items.count).stringValue;
        self.prizeTypeImageView.image = [self prizeTypeImage];
        self.labelDispatchDate.text = [df stringFromDate:self.package.dispatchDate];
        self.labelPeriod.text = [NSString stringWithFormat:@"%@ – %@",
                                 [df stringFromDate:self.package.periodBegin],
                                 [df stringFromDate:self.package.periodEnd]];
    }
    


    On Titanium, you need to write everything in code, it looks like this:

    Hidden text
    var rowTemplate = function(item, index, callback) {
      var row = Ti.UI.createTableViewRow({
        height: rowHeight,
        left: 0,
        right: 0,
        selectedBackgroundColor: '#11a2c5',
        backgroundSelectedColor: '#11a2c5',
        color: 'transparent',
        className: classNameStr
      });
      var view = Ti.UI.createView ({
        top: 0,
        left: leftOffset,
        right: rightOffset,
        height: rowHeight,
        color: 'transparent'
      });
      var backView = Ti.UI.createView ({
        top: 0,
        left: 0,
        right: voucherWidth,
        height: rowHeight,
        color: 'transparent'
      });
      view.add(backView);
      var imageView = Ti.UI.createImageView({
        top: top,
        left: 0,
        width: getControlSize(47),
        height: getControlSize(58),
        image: getPicturePath('/images/orders/icon_oteli_mini')
      });
      backView.add(imageView);
      var left = imageView.left + imageView.width + topOffset;
      var nameLabel = createCommonLabel({
        left: left,
        top: top,
        right: topOffset,
        height: imageView.height,
        font: {
          fontSize: fontSize
        },
        color: '#000000',
        text: itemName
      }, 2);
      backView.add(nameLabel);
      top = imageView.top + imageView.height;
      var placeLabel = createCommonLabel({
        left: 0,
        top: top,
        right: 0,
        height: height,
        font: {
          fontSize: fontSize
        },
        color: '#000000',
        text:  itemCity
      });
      backView.add(placeLabel);
      top = placeLabel.top + placeLabel.height;
      var dateLabel = createCommonLabel({
        left: 0,
        top: top,
        right: 0,
        height: height,
        font: {
          fontSize: fontSize
        },
        color: '#000000',
        text: datesText
      });
      backView.add(dateLabel);
      var voucherView = Ti.UI.createImageView({
        width: voucherWidth,
        top: topOffset,
        right: 0,
        height: getControlSize(48),
        backgroundImage: getPicturePath(voucherImagePath),
        visible: voucherActive
      });
      view.add(voucherView);
      top = dateLabel.top + dateLabel.height;
      var numberLabel = createCommonLabel({
        left: 0,
        top: top,
        right: 0,
        height: height,
        text: orderNumber,
        font: {
          fontSize: fontSize
        },
        color: '#000000'
      });
      backView.add(numberLabel);
      top = numberLabel.top + numberLabel.height;
      var statusLabel = createCommonLabel({
        left: 0,
        top: top,
        right: 0,
        height: height,
        font: {
          fontSize: fontSize
        },
        color: '#000000',
        text: statusText
      });
      backView.add(statusLabel);
      return row;
    };
    


    Titanium 3.1.0 has a new ability to implement a list - ListView. On Android, it works much faster than the table, and the implementation of lists on the ListView allows you to make an application that will not slow down and freeze when scrolling through the list. To do this, rewrite the cell creation as a template:

    Hidden text
    var listItemTemplate = {
      properties: {
        height: rowHeight,
        backgroundSelectedColor: '#11a2c5'
      },
      childTemplates: [
        {
          type: 'Ti.UI.View',
          bindId: 'rootView',
          properties: {
            left: leftOffset,
            top: 0,
            right: rightOffset,
            backgroundSelectedColor: '#11a2c5',
            height: rowHeight,
            color: 'transparent'
          },
          childTemplates: [
            { // backView
              type: 'Ti.UI.View',
              bindId: 'backView',
              properties: {
                left: 0,
                top: 0,
                color: 'transparent',
                backgroundSelectedColor: '#11a2c5',
                right: voucherSize,
                height: rowHeight
              },
              childTemplates: [
                {
                  type: 'Ti.UI.ImageView',
                  bindId: 'icon',
                  properties: {
                    left: 0,
                    top: top,
                    width: getControlSize(47),
                    height: height,
                    image: getPicturePath('/images/icon_mini')
                  }
                },
                {
                  type: 'Ti.UI.Label',
                  bindId: 'name',
                  properties: {
                    left: left,
                    top: top,
                    right: top,
                    height: height,
                    font: {
                      fontSize: fontSize,
                      fontFamily: fontName
                    },
                    color: '#000000'
                  }
                },
                {
                  type: 'Ti.UI.Label',
                  bindId: 'country',
                  properties: {
                    left: 0,
                    top: top + height,
                    right: 0,
                    height: subHeight,
                    font: {
                      fontSize: fontSize,
                      fontFamily: fontName
                    },
                    color: '#000000'
                  }
                },
                {
                  type: 'Ti.UI.Label',
                  bindId: 'date',
                  properties: {
                    left: 0,
                    top: top + height + subHeight,
                    right: 0,
                    height: subHeight,
                    font: {
                      fontSize: fontSize,
                      fontFamily: fontName
                    },
                    color: '#000000'
                  }
                },
                {
                  type: 'Ti.UI.Label',
                  bindId: 'number',
                  properties: {
                    left: 0,
                    top: top + height + subHeight + subHeight,
                    right: 0,
                    height: subHeight,
                    font: {
                      fontSize: fontSize,
                      fontFamily: fontName
                    },
                    color: '#000000'
                  }
                },
                {
                  type: 'Ti.UI.Label',
                  bindId: 'status',
                  properties: {
                    left: 0,
                    top: top + height + subHeight + subHeight + subHeight,
                    right: 0,
                    height: subHeight,
                    font: {
                      fontSize: fontSize,
                      fontFamily: fontName
                    },
                    color: '#000000'
                  }
                }
              ]
            },
            {
              type: 'Ti.UI.ImageView',
              bindId: 'icon',
              properties: {
                right: 0,
                top: top,
                width: voucherSize,
                height: voucherSize,
                image: getPicturePath('/images/icon_act')
              }
            }
          ]
        }
      ]
    };
    


    Since the implementation on ListView completely changes the code for creating the screen of the application, it makes sense to transfer the creation of this screen for iPhone and Android to various files and add the conditions for connecting one of them in accordance with the platform on which the application is running.

    Titanium SDK updated later operating system SDK

    Not so long ago, Apple released iOS 7, where the graphics were substantially redesigned, working with the status bar (the upper part of the screen, which displays the battery level, time and operator), and more.

    What is the usual way to launch a new operating system on the market? First to third-party developers, i.e. developers of mobile applications are given access to the beta version of the new SDK to create applications for the new operating system. Those. everyone who wants to release an application that supports the latest system features can and should prepare in advance. Then a stable SDK appears and begin to be accepted for verification in the AppStore applications with support for the new version of iOS. Then, users - owners of iPhones - become available to update the operating system and have time to prepare applications.

    What happens to those who develop the application on Titanium? The first beta version of the iOS SDK has been available to developers since June 11, 2013, and the version of the Titanium SDK with iOS beta support has been available since August 15 (SDK 3.1.2). Applications with support for the new version of iOS were accepted for verification from September 11, 2013. The

    operating system was available to users from September 18, 2013: the developers had a week to pass the Apple test.

    Titanium released version 3.1.3 RC on September 9, and the stable version 3.1.3 with iOS 7 support only on September 18, i.e. when the moment to be the first on the market was already irrevocably missed.

    In version 3.1.2, support for the normal behavior of the StatusBar was not implemented. Firstly, if the application used to work in full screen mode, now it deviates from the top edge of the screen by the size of the status bar, and the contents of the screens may be located completely unexpectedly, depending on how the coordinates of the elements were set when creating the application. But even if you decided to make an application that does not work in full screen mode, the status of the bar would still not work as expected - instead of the status of the bar, a black bar is displayed without any information: no clock, no charge, no operator is displayed on it . With the release of Titanium 3.1.3, the situation has improved markedly, however, in the photographing mode, the status bar appears even for the full-screen application jira.appcelerator.org/browse/TIMOB-15203 - This error was fixed only in the version of Titanium 3.2.0, which was released on December 20.

    Each version of the SDK contains bug fixes and introduces new bugs

    On the example of the same version 3.1.3: when switching to it from version 3.1.2, the animations in the application significantly slowed down. For example, we need two interface elements to move smoothly from left to right at the same time. In native applications, the developer can describe all the animations that need to be applied to the interface elements and run the animations at the same time. In previous versions of Titanium, the description of animations for individual elements was consistent, but it started almost simultaneously, so that it was visually perceived as a single animation acting with several objects. In Titanium 3.1.3, time differences between sequentially running animations became very noticeable and can be 1-3 seconds. At the end of the animation, you usually need to perform some kind of operations, change the position of elements (the animation itself does not change them), delete objects that are no longer visible so that they do not occupy memory. In Titanium 3.1.3, invoking animation post-processing does not always happen. If you want to repair the status bar, update the SDK version. Yes, your animations will stop working. So what. Update further. You can simply never make an application that can pass Apple's test, waiting for the Titanium bugs to be fixed. Or you can’t even collect it.

    In our practice, there was a case when updating Titanium Studio - the development environment that is used to build applications - led to the fact that the build function of the application for publishing simply stopped working: jira.appcelerator.org/browse/TC-1322 (SDK 2.1.3 RC2 will not build for iTunes store distirbution) or jira.appcelerator.org/browse/TC-2193 (Studio 3.1 Distribute for iTunes Store is producing ad-hoc builds) - when you try to build an application for publication in the App Store, an application for AdHoc distribution is built - in this case, reinstalling the studio helped.

    In conclusion, I would like to note that someone can say that all this nonsense and problems can be circumvented. There is only one question - is it necessary to do this and is it necessary to do this due to the quality and convenience of applications, lagging behind the market, constantly increasing maintenance costs? We decided for ourselves that it is not worth it - specialized tasks need to be solved with the tools created for this.