From wiki.ginstr.com
Jump to: navigation, search

Customer buying goods (app example)

Translating app specification to app files

We have a requirement to make an app for a store from which customers are buying items.

The shopkeeper will use the app to monitor purchases on ginstr cloud by using ginstr launcher as input device.

Each of the items in store have a name and price.

Before finishing their purchase, each customer has to leave a comment and signature.

App should collect following data for each purchase:
  • first and last name of the customer
  • item name
  • item price
  • item pictures
  • customer signature
  • additional comment.

We will split this process into 3 separate pages:

  1. collecting first and last name of the customer
  2. collecting item name, price and pictures
  3. collecting customer signature and additional comments

Note: in real apps there would be app specifications. Along with app business logic description, there would also be defined number of pages and how each page should look and behave. The above specification is simplified in order to aid understanding.

To start process of making ginstr app we must first create the minimum required app folder structure.

Create root folder testGinstrApp1 (in real app specification root folder name will be provided as app ID).

Inside root folder create folders:

"control" folder

This folder must contain XML file widget_en_login.xml, which is a core part for login page in app.

Create in text editor new file and paste following XML code:

<?xml version="1.0" encoding="utf-8"?>
<ScrollView 
	xmlns:android="http://schemas.android.com/apk/res/android"
	android:layout_width="match_parent"
	android:layout_height="match_parent"
	android:fillViewport="true" >

	<LinearLayout
		android:layout_width="match_parent"
		android:layout_height="wrap_content"
		android:orientation="vertical" >

		<TextView
			android:id="@+id/tvCreateUser"
			android:layout_width="wrap_content"
			android:layout_height="wrap_content"
			android:layout_gravity="right"
			android:layout_marginBottom="10dp"
			android:layout_marginLeft="10dp"
			android:clickable="true"
			android:textColor="#ffffff"
			android:text="@string/tvLoginSignUp" />

		<com.ginstr.widgets.GnEditText
			android:id="@+id/etUserName"
			android:layout_width="match_parent"
			android:layout_height="wrap_content"
			android:background="@drawable/entering_field_transparent_2.9"
			gn:s_hint="@string/hint_email"
			gn:s_sourceType="input"
			gn:imeOptions="actionNext"
			gn:imeActionNext="FOCUS_DOWN"
			android:textColor="#FFFFFF"	
			android:textColorHint="#C4C4C4"	/>

		<com.ginstr.widgets.GnEditText
			android:id="@+id/etPassword"
			android:layout_width="match_parent"
			android:layout_height="wrap_content"
			android:layout_marginTop="10dp"               
			android:background="@drawable/entering_field_transparent_2.9"
			gn:s_hint="@string/hint_password"
			gn:imeOptions="actionDone"
			gn:s_sourceType="input"
			android:textColor="#FFFFFF"
			android:textColorHint="#C4C4C4" />

		<RelativeLayout
			android:layout_width="match_parent"
			android:layout_height="wrap_content" >

			<LinearLayout
				android:layout_width="match_parent"
				android:layout_height="wrap_content"
				android:orientation="horizontal"
				android:weightSum="2" >

				<LinearLayout
					android:layout_width="0dp"
					android:layout_height="wrap_content"
					android:layout_weight="1"
					android:weightSum="7" >

					<Button
						android:id="@+id/bLogin"
						android:layout_width="0dp"
						android:layout_height="wrap_content"
						android:layout_weight="6" 
						android:layout_marginTop="10dp"
						android:textSize="20sp" 
						android:text="@string/login"
						android:background="@drawable/button_half.9"
						gn:setBackgroundPressed="@drawable/button_half_pressed.9"
						android:textColor="#ffffff" />
					</LinearLayout>
				</LinearLayout>

				<LinearLayout
					android:id="@+id/lytShowPw"
					android:layout_width="wrap_content"
					android:layout_height="wrap_content"
					android:layout_alignParentRight="true"
					android:orientation="horizontal" >

					<TextView
						android:layout_width="wrap_content"
						android:layout_height="wrap_content"
						android:text="@string/tvLoginShowPassword" 
						android:textColor="#FFFFFF"/>

					<CheckBox
						android:id="@+id/chkShowPw"
						android:layout_width="wrap_content"
						android:layout_height="wrap_content"
						android:textColor="#FFFFFF"
						android:textSize="14sp" />
				</LinearLayout>
			</RelativeLayout>

			<TextView
			android:id="@+id/tvReset"
			android:layout_width="wrap_content"
			android:layout_height="wrap_content"
			android:layout_gravity="right"
			android:layout_marginLeft="10dp"
			android:layout_marginTop="10dp"
			android:clickable="true"
			android:textColor="#FFFFFF" />
	</LinearLayout>

</ScrollView>

The above XML code is standard internal XML structure for login screen widget and doesn’t require any significant changes per each new app. It consists of multiple widget. Widgets in above XML code which have attribute id set are important for further discussion. Usually, widget id is set as first attribute, but in general, the order of widget attributes within the XML file does not affect behaviour of widget.

Widget @+id/tvCreateUser is showing in login screen “sign up” text which is acting as a URL hyperlink. Clicking the text opens in browser [shop.ginstr.com], so user can make registration of ginstr products. Notice that string resource @string/tvLoginSignUp is not yet present in strings.xml, but login screen has displayed text “sign up”.

There are already prepared strings as part of ginstr launcher default ginstr app. These strings are located on device in com.ginstr/$defaultApp/values/strings.xml. This is also the case for other string resources in widget_en_login.xml, such as @string/hint_email.

Widgets with id’s @+id/etUserName and @+id/etPassword are used for inputting user credentials in login screen.

Button @+id/bLogin is executing process of authentication of entered credentials.

Other attributes of these widgets are graphical resources of input boxes and buttons. For instance, @+id/etUserName has background reference to @drawable/entering_field_transparent_2.9, which is then retrieved from drawable folders.

Only one graphic resource will be put to widget, depending on phone screen density. Some widgets have two image resources, for instance button @+id/bLogin have background @drawable/button_half.9, and when button is pressed this another graphic is replacing background image. This is defined with attribute gn:setBackgroundPressed.

Further discussion regarding login screen will be continued with introducing start.xml in folder layout.

"database" folder

Database folder contains the following XML files:

configuration.xml
table schemes (not yet implemented)
queries.xml
prepared query schemes with tables, columns and keys
variables.xml
variable definitions used in app

configuration.xml

Creating this file is used to create tables on back-end side which table uses (not yet implemented, for now tables are created with XML files in layout).

Each table and column have strict naming convention which is documented here.

Further discussion will clearer after introducing widget types and app business logic.

queries.xml

This XML file is used for querying data in app. Each of those actions for interacting with database must have a defined prepared query which relates to table definition.

XML code below will be used for this example app:

<?xml version="1.0" encoding="utf-8"?>
<queries>
	<query 
		id="itemReports" 
		columns="itemReport_firstName,
                         itemReport_lastName,
                         itemReport_itemName,
                         itemReport_itemPrice,
                         itemReport_itemPictures,
                         itemReport_signatureEmployee,
                         itemReport_additionalComments,
                         itemReport_username,
                         itemReport_timestamp,
                         itemReport_gpsLocation,
                         itemReport_address"
        />	
</queries>

In the XML code above, it is important to note that <queries> is root parent and will have several <query> children.

Each <query> will have id which relates to table name. Columns are always same as table field names in backend. Naming scheme for tables and columns is following: tableNames (plural) for table name and for columns tableName_columnName. In our case, table name is itemReports and for column names itemReport_ is used as prefix to column names.

variables.xml

This XML file contains definitions of variables used in app for keeping values of widgets, calculation results or other helper task for implementing business logic. Each variable has id (needed for referencing in actions) and type.

Example of XML code:

<?xml version="1.0" encoding="utf-8"?>
<variables>
	<variable id="firstName" type="text"></variable>
</variable>

In the XML code above, we have only one variable with id (variable name) firstName which is of data type text. Other data types are number, date, etc.

More information about variables can be found here.

"debug" folder

This folder is not intended for inserting XML file. Instead, it is used to save various app logs (if enabled in ginstr launcher preferences) for analysis by ginstr launcher framework developers.

"drawable" folders

Drawable folders are populated with graphical resources referenced in settings/graphic.xml. Those files will be provided to developer via specification for SVN (subversion) locations or provided by other developers.

More information on drawable folders can be found here.

"layout" folder

In this folder, the main XML files are stored which constitute app design and business logic.

The following XML files are required for each app:

activity.xml

This XML file is not intended for modification by XML developer for standard ginstr apps, but it must still be present within the each ginstr app must have this present within "layout" folder.

Create activity.xml file and copy paste following code:

<RelativeLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:gn="http://schemas.ginstr.com/ginstr"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_alignParentBottom="true" >
        <com.ginstr.layout.FragmentViewPager
            android:id="@+id/fragmentPager"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </FrameLayout>
</RelativeLayout>

start.xml

Even though login screens for ginstr apps are standardised by rules, some modifications are needed for each app.

For now, the following XML code can be copied & pasted:

<ScrollView
	xmlns:android="http://schemas.android.com/apk/res/android"
    	xmlns:tools="http://schemas.android.com/tools"
    	xmlns:gn="http://schemas.ginstr.com/ginstr"
    	android:id="@+id/start"
    	android:layout_width="match_parent"
    	android:layout_height="match_parent"
    	android:fillViewport="true"
    	gn:act_beforeLoad="[gn:act_setWinBgd]|[@drawable/background]"
	gn:act_afterLoad="[gn:act_lockScreenOrientation]|[portrait]">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >

	  <com.ginstr.widgets.GnPreferencesButton
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:layout_alignParentRight="true"
		android:background="@drawable/settings"
		gn:setBackgroundPressed="@drawable/settings_pressed" />

        <ImageView
            android:id="@+id/icon"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerHorizontal="true"
            android:layout_marginTop="50dp"
            android:src="@drawable/app_icon"/>

	  <TextView
		android:id="@+id/txtAppName"
		android:layout_width="match_parent"
		android:layout_height="wrap_content"
		android:layout_below="@+id/icon"
           	android:layout_marginLeft="20dp"
           	android:layout_marginRight="20dp"
           	android:layout_marginTop="20dp"
           	android:layout_marginBottom="10dp"
		android:gravity="center_horizontal"
		android:text="@string/appName"
		android:textColor="#FFFFFF"
		android:textSize="30sp"
		android:typeface="sans" />

        <com.ginstr.widgets.GnLogin
            android:id="@+id/btnLogin"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@+id/txtAppName"
            android:layout_centerHorizontal="true"
            android:layout_marginLeft="20dp"
            android:layout_marginRight="20dp"
            gn:act_toLayoutLogin="@+id/page1" />

		<RelativeLayout
            	android:layout_width="wrap_content"
            	android:layout_height="wrap_content"
            	android:layout_below="@+id/btnLogin">

			<ImageView 
				android:id="@+id/footer"
				android:layout_width="match_parent"
				android:layout_height="wrap_content"
				android:layout_alignParentBottom="true"
				android:src="@drawable/background_i"/>

		</RelativeLayout>

    </RelativeLayout>
</ScrollView>

Screen widgets like app icon, app name and background images are defined in XML. While GnLogin structure is loaded from control/widget_en_login.xml file. Purpose and usage of special widgets for instance GnPreferencesButton and GnLogin are described on their respective pages, linked to by widget name.

Root layout have id @+id/start and two attached events. Event gn:act_beforeLoad is executing action [gn:act_setWinBgd]|[@drawable/background] before screen is loaded setting background of the screen and action gn:act_afterLoad is executing action [gn:act_lockScreenOrientation]|[portrait] after screen is loaded, fixing app orientation to portrait mode on device. Again, further information about specific actions and widgets can be found on their respective pages.

It is important to notice that events are defined on widgets like any other attributes. Actions have signature [gn:act_actionName]|[actionParameters]. Brackets and pipes must not have spaces between. For making chain of several actions separator comma (,) is used. In special case separator semicolon (;) is used between 2 actions if used in action IF block.

Graphic resources for login screen app_icon, background and background_i are located in SVN folder appsGraphic\appSpecifics\appId where drawable-xxx folders are located. Other graphic resources for buttons, input boxes and others arelocated in SVN appsGraphic\__themes\themeName where themeName is defined in settings\common.xml. If access to these folders is not permitted, request graphic resources from other developers.

Widget @+id/GnLogin has event with action gn:act_toLayoutLogin="@+id/page1", which is executing loading screen (XML file in folder layout) with @+id/page1 attribute android:id when authentication is verified. Id @+id/page1 will be searched in root layout for every XML files in this folder. Because of this, id of root layouts must have unique names. It is good practice to have the same name for XML file and for root layout.

Creating other app XML files in this folder will be discussed after going through the other folders required for each app.

"settings" folder

This folder is used for documenting app graphic resources, app changes made during development and other description information needed for each ginstr app.

Files that must be present:

changelog.xml

The main purpose of this XML file is to document app changes during development, where several developers can write what has been changed and when. For initial app release, file will have following XML code:

<?xml version="1.0" encoding="utf-8"?>
<changes>
	<change developer="Danijel Škvorc" timestamp="12.01.2016">
		<item>initial release</item>
	</change>
</changes>

After each change developer adds another record, for instance:

<change developer="Danijel Škvorc" timestamp="13.01.2016">
		<item>changing headline color, #12234 note 12</item>
	</change>

It is good practice to have short description of each change, where detailed description can be documented in app ticket in redmine tracking system.

#ticketNumber
represents redmine ticket and note related to changes executed described in more detail

common.xml

In this XML file, app specifics are located, checked internally by ginstr launcher on each ginstr app start.

Below is an example which can be copied & pasted:

<?xml version="1.0" encoding="utf-8"?>
<commonResources>
	<string name="min_client_version">30160107</string>
	<string name="app_version">2.12</string>
	<string name="inSettingsImageSource">Terrace cleaning with high-pressure,&lt;BR/&gt;#36911, © Marina Lohrbach,&lt;BR/&gt;istockphoto.com</string>
</commonResources>

Value of min_client_version is related to ginstr launcher version in which ginstr app can be used. If this number is higher than used ginstr launcher, then user will be notified to download newer ginstr launcher version.

ginstr app developer will change those values when required with newer ginstr launcher supporting latest features. Number is formed as 3yyyMMdd.

Values for app_version developer increases by one each time a new update is made to app. Default value for initial app version is 2.01.

Images that app uses (located in drawable folders) are provided by graphic designer and this information is displayed in specification on redmine for each app.

graphics.xml

Each ginstr app has different set of graphical resources which can be separated to two groups.

  1. images used (mainly) for login screen: app_icon, background and background_i. Those images are located on SVN appsGraphic\appSpecifics\appId.
  2. images for buttons, input boxes, picture widget and others, located on SVN appsGraphic\__themes\themeName.

Example of XML code in this file is as follows:

<graphic>
	<string name="themeName">blueWhite</string>
	<string name="shapeName">mixedRounded</string>
	<string name="readElements">none</string>
	<string name="pagesElements">none</string>
	<string name="spinnerElements">spinnerLight</string>
</graphic>

Each themeName folder has several shapeName folders which can app use. Other strings contain information for specific graphic resources located on appsGraphic\__themes. All those strings are provided in app specification and developers retrieve those groups of images and combine them for each app.

"values" folder

This folder has string resources used in app for headlines, labels, hints, toast messages and others.

For each app to be internationalised, several folders can be created for different languages.

At present, ginstr launcher supports the following folders additional languages:

values
German language
values-en
English language
values-es
Spanish language
values-eg
Arabic language

When a different language is selected in ginstr launcher preferences, the respective folder is selected as source of string resources, referenced in app XML layouts.

As a minimum, it is required to make folders values and values-en, while others are required as per app specification.

Each folder will always have strings.xml and optionally arrays.xml.

strings.xml

XML strings in here will contain text used in ginstr app.

Each app must have following strings:

<?xml version="1.0" encoding="utf-8"?>
<resources>
 <string name="appName">building maintenance management</string>
 <string name="appNameShortcut">building maintenance management</string>
 <string name="in_settingsAppName">building maintenance management</string>
 </string>
</resources>

Each string value should be provided in app specification for each language.

arrays.xml

This XML file will contain strings to be loaded in app, allowing the user to select from some predefined values.

Dropdown widget GnDropDown will use those values.

This XML file is optional and not required for ginstr app to function.

Example content for this file:

<?xml version="1.0" encoding="utf-8"?>
<resources>
 <string-array name="yes_no">
  <item>yes</item>
  <item>no</item>
 </string-array>
</resources>

string-array name will be reference in dropdown widget.

Usage of arrays.xml will be explained in Passenger transportation company app example.

$defaultApp resources

ginstr launcher has prepared string resources for usage in ginstr launcher and ginstr apps. Those strings.xml files can be found on device in $defaultApp folder on device in same path as other installed ginstr apps. This folder is automatically generated with ginstr launcher installation. In general, those resources are the source of strings for ginstr launcher. Those string resources are also the source of common strings which are repeated in ginstr apps and they can be used without storing them in specific app values folder.

Example for this is the following string:

<string name="txtNoWriteRights">Saving data is not permitted for this user account. Please contact your administrator.</string>

Checking create rights before saving data is obligatory and error message is prepared. Note that making same string name in app values folder will overwrite string value for designated ginstr app.

ginstr developer should be informed about $defaultApp string resources.

Implementing app business logic

Since it is required for this app to have 3 pages, we will create empty page1.xml, page2.xml and page3.xml files in folder layout.

Additionally, we need another XML file (itemReportsTABLE.xml) which will be used to create table and columns.

itemReportsTABLE.xml

Create file itemReportsTABLE.xml and copy/paste XML code below:

<LinearLayout
	xmlns:android="http://schemas.android.com/apk/res/android"
	xmlns:tools="http://schemas.android.com/tools"
	xmlns:gn="http://schemas.ginstr.com/ginstr"
	android:id="@+id/itemReportsTABLE"
	android:layout_width="match_parent"
	android:layout_height="match_parent">

	<com.ginstr.widgets.GnEditText
		android:id="@+id/itemReport_firstName"
		android:layout_width="match_parent"
		android:layout_height="wrap_content"
		gn:data_field="itemReport_firstName"
		gn:data_request="itemReports"
		gn:s_sourceType="input"/>

	<com.ginstr.widgets.GnEditText
		android:id="@+id/itemReport_lastName"
		android:layout_width="match_parent"
		android:layout_height="wrap_content"
		gn:data_field="itemReport_lastName"
		gn:data_request="itemReports"
		gn:s_sourceType="input"/>

	<com.ginstr.widgets.GnEditText
		android:id="@+id/itemReport_itemName"
		android:layout_width="match_parent"
		android:layout_height="wrap_content"
		gn:data_field="itemReport_itemName"
		gn:data_request="itemReports"
		gn:s_sourceType="input"/>

	<com.ginstr.widgets.GnEditText
		android:id="@+id/itemReport_itemPrice"
		android:layout_width="match_parent"
		android:layout_height="wrap_content"
		gn:data_field="itemReport_itemPrice"
		gn:data_request="itemReports"
		gn:dataType="number"
		gn:s_decimalFormat="0.00"
		gn:s_sourceType="input"/>

	<com.ginstr.widgets.GnMediaAction
		android:id="@+id/itemReport_itemPictures"
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		gn:data_field="itemReport_itemPictures"
		gn:data_request="itemReports"
		gn:maxFileSize="75"
		gn:s_actionType="photo" />

	<com.ginstr.widgets.GnCaptureSignature
		android:id="@+id/itemReport_signatureEmployee"
		android:layout_width="match_parent"
		android:layout_height="wrap_content"
		gn:data_field="itemReport_signatureEmployee"
		gn:data_request="itemReports" />

	<com.ginstr.widgets.GnEditText
		android:id="@+id/itemReport_additionalComments"
		android:layout_width="match_parent"
		android:layout_height="wrap_content"
		gn:data_field="itemReport_additionalComments"
		gn:data_request="itemReports"
		gn:s_sourceType="input"/>

	<com.ginstr.widgets.GnEditText
		android:id="@+id/itemReport_username"
		android:layout_width="match_parent"
		android:layout_height="wrap_content"
		gn:data_field="itemReport_username"
		gn:data_request="itemReports"
		gn:s_sourceType="aguser"/>

	<com.ginstr.widgets.GnEditText
		android:id="@+id/itemReport_timestamp"
		android:layout_width="match_parent"
		android:layout_height="wrap_content"
		gn:data_field="itemReport_timestamp"
		gn:data_request="itemReports"
		gn:s_sourceType="time"/>

	<com.ginstr.widgets.GnEditText
		android:id="@+id/itemReport_gpsLocation"
		android:layout_width="match_parent"
		android:layout_height="wrap_content"
		gn:data_field="itemReport_gpsLocation"
		gn:data_request="itemReports"
		gn:s_sourceType="gps"/>

	<com.ginstr.widgets.GnEditText
		android:id="@+id/itemReport_address"
		android:layout_width="match_parent"
		android:layout_height="wrap_content"
		gn:data_field="itemReport_address"
		gn:data_request="itemReports"
		gn:showAddress="true"
		gn:s_sourceType="gps"/>
		
</LinearLayout>

Based on those widgets, column for given table will be created. Note attribute gn:data_request for all widgets is “itemReports”, this will be the name of the table.

For each widget, gn:data_field matches to desired column name and also widget id’s are matching with data_field. This is good practice to ensure that each widget has unique id in given XML file, also in case when it is copied to other XML.

Different type of widgets are creating different type of column data type. For instance, @+id/itemReports_firstName is widget GnEditText with gn:s_sourceType=”input”, which will end up being Dt text for column itemReport_firstName<code> in table <code>itemReports.

A slight variation is made on @+id/itemReport_itemPrice where same widget has additional attribute gn:dataType=”number” and gn:s_decimalFormat=”0.00” which makes column itemReport_itemPrice decimal number data type with 2 decimal points.

Another variation of GnEditText is for @+id/itemReport_gpsLocation which with gn:s_sourceType=”gps” makes GPS data type.

Widgets GnMediaAction make picture data type and GnCaptureSignature signature data type.

Once this XML file is created we can upload the app to ginstr web as described here.

page1.xml

XML code for page1.xml is prepared for copy/paste below:

<?xml version="1.0" encoding="utf-8"?>
<ScrollView 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fillViewport="true" >

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical" >

        <TextView
            android:id="@+id/tvCreateUser"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="right"
            android:layout_marginBottom="10dp"
            android:layout_marginLeft="10dp"
            android:clickable="true"
            android:textColor="#ffffff"
            android:text="@string/tvLoginSignUp" />

        <com.ginstr.widgets.GnEditText
            android:id="@+id/etUserName"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@drawable/entering_field_transparent_2.9"
            gn:s_hint="@string/hint_email"
            gn:s_sourceType="input"
            gn:imeOptions="actionNext"
            gn:imeActionNext="FOCUS_DOWN"
            android:textColor="#FFFFFF"
            android:textColorHint="#C4C4C4" />

        <com.ginstr.widgets.GnEditText
            android:id="@+id/etPassword"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            android:background="@drawable/entering_field_transparent_2.9"
            gn:s_hint="@string/hint_password"
            gn:imeOptions="actionDone"
            gn:s_sourceType="input"
            android:textColor="#FFFFFF"
            android:textColorHint="#C4C4C4" />

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content" >

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="horizontal"
                android:weightSum="2" >

                <LinearLayout
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:weightSum="7" >

                    <Button
                        android:id="@+id/bLogin"
                        android:layout_width="0dp"
                        android:layout_height="wrap_content"
                        android:layout_weight="6" 
                        android:layout_marginTop="10dp"
                        android:textSize="20sp" 
                        android:text="@string/login"
                        android:background="@drawable/button_half.9"
                        gn:setBackgroundPressed="@drawable/button_half_pressed.9"
                        android:textColor="#ffffff" />
                </LinearLayout>
            </LinearLayout>

            <LinearLayout
                android:id="@+id/lytShowPw"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentRight="true"
                android:orientation="horizontal" >

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="@string/tvLoginShowPassword" 
                    android:textColor="#FFFFFF"/>

                <CheckBox
                    android:id="@+id/chkShowPw"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:textColor="#FFFFFF"
                    android:textSize="14sp" />
            </LinearLayout>
        </RelativeLayout>

        <TextView
            android:id="@+id/tvReset"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="right"
            android:layout_marginLeft="10dp"
            android:layout_marginTop="10dp"
            android:clickable="true"
            android:textColor="#FFFFFF" />
    </LinearLayout>

</ScrollView>
Screenshot of page 1 of "Customer buying goods" app

After login, page1.xml is loaded.

Important to note are FrameLayout widgets which have stored some procedures, called action sets.

Icon @+id/iconForward has attached click event gn:act_setClick="[gn:act_trigger]|[@+id/continueToNextPage,gn:act_set]" which executes actions set in @+id/continueToNextPage<code>. On screen, this is icon ”>>”

FrameLayout <code>@+id/saveDataPage1 used for making screen validation inside IF statement and also loading page2. Since input boxes are required to enter data defined with attributes gn:act_validate="[name:RequiredValidator]…”, action [gn:act_validateScreen]|[] is examining all validators attached to all widgets on page. If action returns false (some validator detected that condition is not met), toast message @string/validateFillAllPage1 will be shown to user next to that widget or in message box and ginstr app will not allow further execution. Note that separator between actions in if statement is semicolon (;).

Inside @+id/continueToNextPage is encapsulated another action set trigger @+id/saveDataPage1 which has actions to save widget data to variables.

Since widgets are not globally available through all app pages, for further widget data usage we store their values into widgets, for instance with action [gn:act_getVariableToWidget]|[@variable/itemReport_firstName,@+id/itemReport_firstName].

For this we have prepared variables in variables.xml as follows:

<variable id="itemReport_firstName" type="string"></variable>
<variable id="itemReport_lastName" type="string"></variable>
<variable id="itemReport_itemName" type="string"></variable>
<variable id="itemReport_itemPrice" type="number"></variable>
<variable id="itemReport_itemPictures" type="pictures"></variable>
<variable id="itemReport_signatureEmployee" type="signature"></variable>
<variable id="itemReport_additionalComments" type="string"></variable>

It is good practice to name variables as widgets.

For page to have string resources we add into strings.xml:

<string name="txtPage1Headline">customer details</string>

<string name="txtPage1FirstName">first name*</string>
<string name="txtPage1FirstNameHint">first name</string>
<string name="txtPage1FirstNameRequired">entering first name of the customer is required</string>

<string name="txtPage1LastName">last name*</string>
<string name="txtPage1LastNameHint">last name</string>
<string name="txtPage1LastNameRequired">entering last name of the customer is required</string>

<string name="txtPercentageBottomBar1">0%</string> 
<string name="txtBottomBarNext">next</string>
<string name="toastNextPage1">page 2/3</string>
<string name="validateFillAllPage1">please fill all required fields</string>

Making empty lines keeps related strings in logical sections. This is the case with input box which is described with label, hint and validator message.

page2.xml

For page 2 to have input box item name, item price and taking images of item, the following XML code can be copy/pasted into page2.xml

<LinearLayout
	xmlns:android="http://schemas.android.com/apk/res/android"
	xmlns:tools="http://schemas.android.com/tools"
	xmlns:gn="http://schemas.ginstr.com/ginstr"
	android:id="@+id/page2"
	android:layout_width="match_parent"
	android:layout_height="match_parent"
	android:background="@drawable/background"
	android:orientation="vertical"
	gn:act_beforeLoad="[gn:act_cleanWidgets]|[],[gn:act_trigger]|[@+id/preloadDataFromVariables,gn:act_set]"
	gn:act_backLoad="[gn:act_cleanWidgets]|[],[gn:act_trigger]|[@+id/preloadDataFromVariables,gn:act_set]"
	gn:act_setBack="[gn:act_trigger]|[@+id/saveDataPage1,gn:act_set]">

	<TextView
		android:layout_width="match_parent"
		android:layout_height="wrap_content"
		android:layout_marginTop="5dp"		
		android:layout_marginLeft="10dp"
		android:textSize="25sp"
		android:textColor="#6099f3"
		android:text="@string/txtPage2Headline" />

	<ScrollView
		android:layout_width="match_parent"
		android:layout_height="0dp"
		android:layout_weight="0.43"
		android:layout_marginTop="15dp" >

		<LinearLayout
			android:layout_width="match_parent"
			android:layout_height="match_parent"
			android:orientation="vertical"
			android:layout_marginLeft="10dp"
			android:layout_marginRight="10dp" >

			<FrameLayout
				android:id="@+id/preloadDataFromVariables"
				android:layout_width="wrap_content"
				android:layout_height="wrap_content"
				android:visibility="gone"
				gn:act_set="[gn:act_getVariableToWidget]|[@variable/itemReport_itemName,
				            @+id/itemReport_itemName],
				            [gn:act_getVariableToWidget]|[@variable/itemReport_itemPrice,
				            @+id/itemReport_itemPrice],
				            [gn:act_getVariableToWidget]|[@variable/itemReport_itemPictures,
				            @+id/itemReport_itemPictures],
				            [gn:act_copyData]|[@+id/itemReport_itemPictures,
				            @+id/itemReport_itemPicturesView]" />

			<FrameLayout
				android:id="@+id/saveDataPage1"
				android:layout_width="wrap_content"
				android:layout_height="wrap_content"
				android:visibility="gone"
				gn:act_set="[gn:act_setVariable]|[@variable/itemReport_itemName,
				            @+id/itemReport_itemName],
				            [gn:act_setVariable]|[@variable/itemReport_itemPrice,
				            @+id/itemReport_itemPrice],
				            [gn:act_setVariable]|[@variable/itemReport_itemPictures,
				            @+id/itemReport_itemPictures]" />

			<FrameLayout
				android:id="@+id/continueToNextPage"
				android:layout_width="wrap_content"
				android:layout_height="wrap_content"
				android:visibility="gone"
				gn:act_set="[gn:act_if]|[[gn:act_validateScreen]|[]--[gn:act_trigger]|[@+id/saveDataPage1,
				            gn:act_set];[gn:act_toLayoutClick]|[@+id/page3];
				            [gn:act_toast]|[@string/toastNextPage2]::[gn:act_validateScreen]|[@string/validateFillAllPage2]]"/>

			<TextView
				android:layout_width="match_parent"
				android:layout_height="wrap_content"
				android:layout_marginTop="10dp" 
				android:textColor="#6099f3"
				android:textSize="20sp" 
				android:text="@string/txtPage2ItemName" />

			<com.ginstr.widgets.GnEditText
				android:id="@+id/itemReport_itemName"
				android:layout_width="match_parent"
				android:layout_height="wrap_content"
				android:background="@drawable/entering_field_2"
				android:layout_marginTop="5dp" 
				android:layout_marginBottom="10dp"
				android:textSize="20sp"
				android:textColor="#FFFFFF"
				android:textColorHint="#CCCCCC"
				gn:act_validate="[name:RequiredValidator],[message=@string/txtPage2ItemNameRequired]"
				gn:s_sourceType="input"
				gn:s_hint="@string/txtPage2ItemNameHint"/>

			<TextView
				android:layout_width="match_parent"
				android:layout_height="wrap_content"
				android:layout_marginTop="10dp" 
				android:textColor="#6099f3"
				android:textSize="20sp" 
				android:text="@string/txtPage2ItemPrice" />

			<com.ginstr.widgets.GnEditText
				android:id="@+id/itemReport_itemPrice"
				android:layout_width="match_parent"
				android:layout_height="wrap_content"
				android:background="@drawable/entering_field_2"
				android:layout_marginTop="5dp" 
				android:layout_marginBottom="10dp"
				android:textSize="20sp"
				android:textColor="#FFFFFF"
				android:textColorHint="#CCCCCC"
				gn:s_hint="@string/txtPage2ItemPriceHint"
				gn:s_sourceType="input"
				gn:inputType="TYPE_NUMBER_FLAG_DECIMAL"
				gn:dataType="number"
				gn:act_validate="[name:TextValidator],[minLength=1],
				                 [minLengthErrMsg=@string/txtPage2ItemPriceRequired],
				                 [maxLength=15],
				                 [maxLengthErrMsg=@string/errorDecimalLengthLimit],
				                 [inputType=REGEX],
				                 [REGEXErrMsg=@string/errorDecimalPattern],
				                 [pattern=@string/decimalPattern]"
				gn:s_decimalFormat="0.00" />

			<TextView
				android:layout_width="match_parent"
				android:layout_height="wrap_content"
				android:layout_marginTop="10dp" 
				android:textColor="#93bbfb"
				android:textSize="20sp" 
				android:text="@string/txtPage2ItemPhotos" />

			<LinearLayout
				android:layout_width="match_parent"
				android:layout_height="wrap_content"
				android:orientation="vertical"
				android:layout_marginTop="5dp"
				android:layout_marginBottom="5dp"
				android:background="#44000000"
				android:padding="5dp">

				<RelativeLayout
					android:layout_width="match_parent"
					android:layout_height="wrap_content">

					<com.ginstr.widgets.GnMediaAction
						android:id="@+id/itemReport_itemPictures"
						android:layout_width="wrap_content"
						android:layout_height="wrap_content"
						android:layout_marginRight="5dp"
						android:layout_alignParentRight="true"
						gn:act_setAfterMediaTaken="[gn:act_copyData]|[@+id/itemReport_itemPictures,
				                 			   @+id/itemReport_itemPicturesView]"
						gn:maxMediaTaken="2"
						gn:maxFileSize="75"
						gn:s_actionType="photo" />

				</RelativeLayout>

				<com.ginstr.widgets.GnMediaGallery
					android:id="@+id/itemReport_itemPicturesView"
					android:layout_width="match_parent"
					android:layout_height="wrap_content"
					gn:numberOfColumns="2"
					gn:itemHorizontalSpacing="5"
					gn:itemVerticalSpacing="5"
					gn:itemSameSize="true" />	
			</LinearLayout>

		</LinearLayout>
	</ScrollView>

	<RelativeLayout
		android:layout_width="match_parent"
		android:layout_height="wrap_content"
		android:layout_marginTop="5dp"
		android:background="#000000" >

		<ImageView
			android:id="@+id/iconBack"
			android:layout_width="wrap_content"
			android:layout_height="wrap_content"
			android:layout_alignParentLeft="true"
			android:layout_centerVertical="true"
			android:layout_marginLeft="5dp"
			android:src="@drawable/icon_back_2" 
			gn:act_setClick="[gn:act_goLayoutBack]|[1]"
			gn:setBackgroundPressed="@drawable/icon_back_2_pressed" />

		<ImageView
			android:id="@+id/iconForward"
			android:layout_width="wrap_content"
			android:layout_height="wrap_content"
			android:layout_alignParentRight="true"
			android:layout_centerVertical="true"
			android:layout_marginRight="5dp"
			gn:act_setClick="[gn:act_trigger]|[@+id/continueToNextPage,gn:act_set]"
			android:src="@drawable/icon_forward_2" 
			gn:setBackgroundPressed="@drawable/icon_forward_2_pressed" />

		<TextView
			android:id="@+id/txtBottomBarNext"
			android:layout_width="wrap_content"
			android:layout_height="wrap_content"
			android:layout_centerInParent="true"
			android:layout_marginRight="8dp"
			android:textSize="18sp"
			android:layout_toLeftOf="@+id/iconForward"
			android:text="@string/txtBottomBarNext"
			android:textColor="#ffffff" />

		<TextView
			android:id="@+id/txtPercentageBottomBar2"
			android:layout_width="wrap_content"
			android:layout_height="wrap_content"
			android:layout_alignBaseline="@+id/txtBottomBarNext"
			android:layout_centerVertical="true"
			android:layout_centerHorizontal="true"
			android:textSize="18sp"
			android:text="@string/txtPercentageBottomBar2"
			android:textColor="#ffffff" />

	</RelativeLayout>
</LinearLayout>
Screenshot of page 2 of "Customer buying goods" app

Root layout @+id/page2 has attached for action before page loads, on going back to previous page and getting back to this page from page 3.

By following triggered action sets, we can conclude that those actions are related to saving widget data to variables or setting variables back to widgets. This ensures that data which user has previously entered is not lost.

Handling data during app execution is an important part of implementing business logic. In some situations, variables may not be required. With more experience it will be clear which situations require data to be kept in widgets or alternatively in variables. In general, new app instance will have empty widgets and variables.

For string resources we will add the following lines into strings.xml:

 <string name="txtPage2Headline">item details</string>

 <string name="txtPage2ItemName">item name*</string>
 <string name="txtPage2ItemNameHint">item name</string>
 <string name="txtPage2ItemNameRequired">entering item name is required</string>
 
 <string name="txtPage2ItemPrice">item price*</string>
 <string name="txtPage2ItemPriceHint">item price</string>
 <string name="txtPage2ItemPriceRequired">entering item price is required</string>
 
 <string name="txtPage2ItemPhotos">item photos (max 2)</string>
 
 <string name="txtPercentageBottomBar2">50%</string>
 <string name="toastNextPage2">page 3/3</string>
 <string name="validateFillAllPage2">please fill all required fields</string>

Notice how some triggers are cloned from previous page and only action sets adapted to widget and variable id’s. Inputting number is configured differently than plain text input box. Also it has a validator with multiple conditions.

Some string resources are not required to have in app strings.xml, because they are already prepared in $defaultApp string resources and available to use in ginstr apps. This is the case for string @string/errorDecimalLengthLimit, which will be shown to user if user has entered number that has more that 15 digits when validation action occured.

Taking picture requires using widget GnMediaAction which starts camera app of the device. Taken photos are then stored in same widget, but to display the taken photos another widget GnMediaGallery is used. Data (images) are copied from widget to widget. This principle is the same when retrieving data from variable with photos back to widget.

In general, copying data is possible between widgets and variables where data type must be the same. Take a note how widget id’s are constructed, to be intuitive in action sets.

If for some string references in app pages is displayed @string/someStringReference, it means that string with name “someStringReference” is not found in strings.xml.

page3.xml

Page 3 is the last page in app where data is saved to database, widgets and variables cleaned and app returns to page 1.

<LinearLayout
	xmlns:android="http://schemas.android.com/apk/res/android"
	xmlns:tools="http://schemas.android.com/tools"
	xmlns:gn="http://schemas.ginstr.com/ginstr"
	android:id="@+id/page3"
	android:layout_width="match_parent"
	android:layout_height="match_parent"
	android:background="@drawable/background"
	android:orientation="vertical"
	gn:act_beforeLoad="[gn:act_cleanWidgets]|[],[gn:act_trigger]|[@+id/preloadDataFromVariables,gn:act_set]"
	gn:act_setBack="[gn:act_trigger]|[@+id/saveDataPage1,gn:act_set]">

	<TextView
		android:layout_width="match_parent"
		android:layout_height="wrap_content"
		android:layout_marginTop="5dp"		
		android:layout_marginLeft="10dp"
		android:textSize="25sp"
		android:textColor="#6099f3"
		android:text="@string/txtPage3Headline" />

	<ScrollView
		android:layout_width="match_parent"
		android:layout_height="0dp"
		android:layout_weight="0.43"
		android:layout_marginTop="15dp" >

		<LinearLayout
			android:layout_width="match_parent"
			android:layout_height="match_parent"
			android:orientation="vertical"
			android:layout_marginLeft="10dp"
			android:layout_marginRight="10dp" >

			<FrameLayout
				android:id="@+id/preloadDataFromVariables"
				android:layout_width="wrap_content"
				android:layout_height="wrap_content"
				android:visibility="gone"
				gn:act_set="[gn:act_getVariableToWidget]|[@variable/itemReport_signatureEmployee,
				            @+id/itemReport_signatureEmployee],
				            [gn:act_copyData]|[@+id/itemReport_signatureEmployee,
				            @+id/itemReport_signatureEmployeeView],
				            [gn:act_getVariableToWidget]|[@variable/itemReport_additionalComments,
				            @+id/itemReport_additionalComments]" />

			<FrameLayout
				android:id="@+id/saveDataPage1"
				android:layout_width="wrap_content"
				android:layout_height="wrap_content"
				android:visibility="gone"
				gn:act_set="[gn:act_setVariable]|[@variable/itemReport_signatureEmployee,
				            @+id/itemReport_signatureEmployee],
				            [gn:act_setVariable]|[@variable/itemReport_additionalComments,
				            @+id/itemReport_additionalComments]" />

			<FrameLayout
				android:id="@+id/saveDataToDatabase"
				android:layout_width="wrap_content"
				android:layout_height="wrap_content"
				android:visibility="gone"
				gn:act_set="[gn:act_if]|[[gn:act_validateScreen]|[]--[gn:act_trigger]|[@+id/saveDataPage1,gn:act_set];
				            [gn:act_reloadAutoFilledWidgets]|[@+id/itemReport_timestamp];
				            [gn:act_rawWriteValues]|[itemReports;itemReport_firstName,
				            itemReport_lastName,
				            itemReport_itemName,
				            itemReport_itemPrice,
				            itemReport_itemPictures,
				            itemReport_signatureEmployee,
				            itemReport_additionalComments,
				            itemReport_username,
				            itemReport_timestamp,
				            itemReport_gpsLocation,
				            itemReport_address;
				            @variable/itemReport_firstName,
				            @variable/itemReport_lastName,
				            @variable/itemReport_itemName,
				            @variable/itemReport_itemPrice,
				            @variable/itemReport_itemPictures,
				            @variable/itemReport_signatureEmployee,
				            @variable/itemReport_additionalComments,
				            @+id/itemReport_username,
				            @+id/itemReport_timestamp,
				            @+id/itemReport_gpsLocation,
				            @+id/itemReport_address];
				            [gn:act_toast]|[@string/toastDataSaved];
				            [gn:act_trigger]|[@+id/cleanVariables,
				            [gn:act_set];
				            [gn:act_cleanWidgets]|[];
				            [gn:act_goLayoutBack]|[@+id/page1]::[gn:act_validateScreen]|[@string/validateFillAllPage3]]"/>

			<FrameLayout
				android:id="@+id/cleanVariables"
				android:layout_width="wrap_content"
				android:layout_height="wrap_content"
				android:visibility="gone"
				gn:act_set="[gn:act_resetVariables]|[@variable/itemReport_firstName,
				            @variable/itemReport_lastName,
				            @variable/itemReport_itemName,
				            @variable/itemReport_itemPrice,
				            @variable/itemReport_itemPictures,
				            @variable/itemReport_signatureEmployee,
				            @variable/itemReport_additionalComments],
				            [gn:act_setVariable]|[@variable/itemReport_firstName,@+id/emptyText],
				            [gn:act_setVariable]|[@variable/itemReport_lastName,@+id/emptyText],
				            [gn:act_setVariable]|[@variable/itemReport_itemName,@+id/emptyText],
				            [gn:act_setVariable]|[@variable/itemReport_additionalComments,@+id/emptyText],
				            [gn:act_setVariable]|[@variable/itemReport_itemPrice,@+id/emptyNumber]" />

			<com.ginstr.widgets.GnEditText
				android:id="@+id/emptyText"
				android:layout_width="match_parent"
				android:layout_height="wrap_content"
				gn:visible="false"
				gn:s_sourceType="input" />

			<com.ginstr.widgets.GnEditText
				android:id="@+id/emptyNumber"
				android:layout_width="match_parent"
				android:layout_height="wrap_content"
				gn:dataType="number"
				gn:s_decimalFormat="0.00"
				gn:visible="false"				
				gn:s_sourceType="input" />

			<TextView
				android:layout_width="match_parent"
				android:layout_height="wrap_content"
				android:layout_marginTop="10dp" 
				android:textColor="#6099f3"
				android:textSize="20sp" 
				android:text="@string/txtPage3CustomerSignature" />

			<com.ginstr.widgets.GnCaptureSignature
				android:id="@+id/itemReport_signatureEmployee"
				android:layout_width="match_parent"
				android:layout_height="wrap_content"
				android:background="@drawable/button_half" 
				gn:setBackgroundPressed="@drawable/button_half_pressed"
				android:layout_marginTop="10dp"
				android:text="@string/txtPage3CustomerSignatureHint"
				android:textColor="#FFFFFF"
				gn:act_validate="[name:RequiredValidator],
				                 [message=@string/txtPage3CustomerSignatureRequired]"
				android:textSize="20sp"
				gn:act_setAfterSignatureTaken="[gn:act_copyData]|[@+id/itemReport_signatureEmployee,
				                               @+id/itemReport_signatureEmployeeView]" />  

			<com.ginstr.widgets.GnMediaGallery
				android:id="@+id/itemReport_signatureEmployeeView"
				android:layout_width="match_parent"
				android:layout_height="wrap_content"
				gn:numberOfColumns="2"
				gn:clearOnNewDataAdd="true"
				gn:itemHorizontalSpacing="5"
				gn:itemVerticalSpacing="5"
				gn:itemSameSize="true" />	

			<TextView
				android:layout_width="match_parent"
				android:layout_height="wrap_content"
				android:layout_marginTop="10dp" 
				android:textColor="#6099f3"
				android:textSize="20sp" 
				android:text="@string/txtPage3AdditionalComments" />

			<com.ginstr.widgets.GnEditText
				android:id="@+id/itemReport_additionalComments"
				android:layout_width="match_parent"
				android:layout_height="wrap_content"
				android:background="@drawable/entering_field_2"
				android:layout_marginTop="5dp" 
				android:layout_marginBottom="10dp"
				android:textSize="20sp"
				android:textColor="#FFFFFF"
				android:textColorHint="#CCCCCC"
				gn:s_hint="@string/txtPage3AdditionalCommentsHint"
				gn:s_sourceType="input" />

		</LinearLayout>
	</ScrollView>

	<RelativeLayout
		android:layout_width="match_parent"
		android:layout_height="wrap_content"
		android:layout_marginTop="5dp"
		android:background="#000000" >

		<ImageView
			android:id="@+id/iconBack"
			android:layout_width="wrap_content"
			android:layout_height="wrap_content"
			android:layout_alignParentLeft="true"
			android:layout_centerVertical="true"
			android:layout_marginLeft="5dp"
			android:src="@drawable/icon_back_2" 
			gn:act_setClick="[gn:act_goLayoutBack]|[1]"
			gn:setBackgroundPressed="@drawable/icon_back_2_pressed" />

		<ImageView
			android:id="@+id/iconForward"
			android:layout_width="wrap_content"
			android:layout_height="wrap_content"
			android:layout_alignParentRight="true"
			android:layout_centerVertical="true"
			android:layout_marginRight="5dp"
			gn:act_setClick="[gn:act_trigger]|[@+id/saveDataToDatabase,gn:act_set]"
			android:src="@drawable/icon_forward_2" 
			gn:setBackgroundPressed="@drawable/icon_forward_2_pressed" />

		<TextView
			android:id="@+id/txtBottomBarDone"
			android:layout_width="wrap_content"
			android:layout_height="wrap_content"
			android:layout_centerInParent="true"
			android:layout_marginRight="8dp"
			android:textSize="18sp"
			android:layout_toLeftOf="@+id/iconForward"
			android:text="@string/txtBottomBarDone"
			android:textColor="#ffffff" />

		<TextView
			android:id="@+id/txtPercentageBottomBar3"
			android:layout_width="wrap_content"
			android:layout_height="wrap_content"
			android:layout_alignBaseline="@+id/txtBottomBarNext"
			android:layout_centerVertical="true"
			android:layout_centerHorizontal="true"
			android:textSize="18sp"
			android:text="@string/txtPercentageBottomBar3"
			android:textColor="#ffffff" />

		<com.ginstr.widgets.GnEditText
			android:id="@+id/itemReport_username"
			android:layout_width="match_parent"
			android:layout_height="wrap_content"
			gn:visible="false"
			gn:s_sourceType="aguser"/>

		<com.ginstr.widgets.GnEditText
			android:id="@+id/itemReport_timestamp"
			android:layout_width="match_parent"
			android:layout_height="wrap_content"
			gn:visible="false"
			gn:s_sourceType="time"/>

		<com.ginstr.widgets.GnEditText
			android:id="@+id/itemReport_gpsLocation"
			android:layout_width="match_parent"
			android:layout_height="wrap_content"
			gn:visible="false"
			gn:s_sourceType="gps"/>

		<com.ginstr.widgets.GnEditText
			android:id="@+id/itemReport_address"
			android:layout_width="match_parent"
			android:layout_height="wrap_content"
			gn:visible="false"
			gn:showAddress="true"
			gn:s_sourceType="gps"/>

	</RelativeLayout>    
</LinearLayout>

Additional strings must be implemented into strings.xml:

 <string name="txtPage3Headline">finish report</string>

 <string name="txtPage3CustomerSignature">customer signature*</string>
 <string name="txtPage3CustomerSignatureHint">customer signature*</string>
 <string name="txtPage3CustomerSignatureRequired">customer signature is required</string>
 
 <string name="txtPage3AdditionalComments">additional comments</string>
 <string name="txtPage3AdditionalCommentsHint">additional comments</string>
 
 <string name="txtPercentageBottomBar3">75%</string>
 <string name="txtBottomBarDone">save</string>
 <string name="validateFillAllPage3">please fill all required fields</string>
 
 <string name="toastDataSaved">report has been saved successfully</string>

On page3.xml we have widgets which are not visible, but they are also saved into database for report. Those widgets are related to saving current GPS of device, time of making report, address (resolved by NOMINATIM service via current GPS of device) and currently logged user. In most cases, each app will save this data for each “reporting” table.

Following app execution, app is validating widgets and action of saving values to database takes place if validation has passed. Since widget @+id/itemReport_timestamp with current time holds current time set when page has loaded, with gn:act_reloadAutoFilledWidgets this time is reloaded just before saving data. Action gn:act_rawWriteValues has 3 groups of parameters separated by ";" (semicolon). The first parameter is table name, second parameter is table column names and third parameter is data containers (widgets, variables) from which is data taken.

Columns and data containers are considered to be in order of pairs, meaning that first column is pairing with first data container, second column in second data container etc. More information about the action in Actions chapter. After saving data, app returns to page1. Saved record data is visible in ginstr web UI.

After saving data we trigger action set @+id/cleanVariables which executes resetting all used variables. Resetting variables set value null to all, regardless of variable type. This may not be desirable for text and number variable data type, so we also set empty widget values to those variables.

Action gn:act_cleanWidgets clears all widget content (on current screen), which must be done every time widgets are stored to database, making all widgets (comparable to form in web forms) empty and ready for new data save.

Using action gn:act_toast informs the user about some actions which concluded app business logic step. In general, toasting messages are used to throw error messages, loading next page and also for debugging purposes in developing app. Widget and variable values can be toasted any time in app execution and with action gn:act_break, app execution can be stopped and widget or variable values can be toasted. Note that toast messages in final app version must always use string references and not hardcoded strings. All debugging actions should also be removed At this point app implements full business logic of making reports for store, documenting customer data, item data, signing by user and saving all those data to database.

Other files now need to be updated to comply with ginstr app test procedure.

In "database" folder, file configuration.xml will have table scheme in following format:

<configuration>
<tables>

<table id="itemReports" name="testGinstrApp1_itemReports">
 <columnText id="itemReport_firstName" name="testGinstrApp1_itemReport_firstName" required="true"/>
 <columnText id="itemReport_lastName" name="testGinstrApp1_itemReport_lastName" required="true"/>
 <columnText id="itemReport_itemName" name="testGinstrApp1_itemReport_itemName" required="true"/>
 <columnNumber id="itemReport_itemPrice" name="testGinstrApp1_itemReport_itemPrice" required="true"/>
 <columnPictures id="itemReport_itemPictures" name="testGinstrApp1_itemReport_itemPictures" />
 <columnSignature id="itemReport_signatureEmployee" name="testGinstrApp1_itemReport_signatureEmployee" required="true"/>
 <columnText id="itemReport_additionalComments" name="testGinstrApp1_itemReport_additionalComments" />
 <columnText id="itemReport_username" name="testGinstrApp1_itemReport_username" />
 <columnTime id="itemReport_timestamp" name="testGinstrApp1_itemReport_timestamp" />
 <columnGPS id="itemReport_gpsLocation" name="testGinstrApp1_itemReport_gpsLocation" />
 <columnText id="itemReport_address" name="testGinstrApp1_itemReport_address" />
</table>

</tables>
</configuration>

Note that id’s are matching to table and column names, and name attributes have appId in prefix.

Other important attributes are settings required for columns which must be filled and saved for each record.

Based on completed configuration.xml, copy and paste to strings.xml.

Each of those strings needs to be modified as follows:

<table id="itemReports" name="testGinstrApp1_locations"> 
replace with <string name="testGinstrApp1_itemReports">reports</string>
<columnText id="itemReport_firstName" name="testGinstrApp1_itemReport_firstName" required="true"/> 
replace with <string name="testGinstrApp1_itemReport_firstName">first name</string> and so on.

Note how strings are created and also string values added. Those strings will be used for user-friendly table and column names in ginstr web UI. In general, the developer makes strings and strings values for folder values-en while other values folders will be translated by other developers or ginstr staff.

It is important that the developer makes strings for all languages and that same id's are also located in same lines throughout all strings.xml files. Note that string names and string values may require later changes by third party and these changes will have to made in all other XML files to which this string is referenced.

After app is completed it must comply with ginstr app test procedure. Learning to develop with test procedure in mind will reduce time for making changes later in bug-fixing period.