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:
- collecting first and last name of the customer
- collecting item name, price and pictures
- 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,<BR/>#36911, © Marina Lohrbach,<BR/>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.
- images used (mainly) for login screen:
app_icon
,background
andbackground_i
. Those images are located on SVN appsGraphic\appSpecifics\appId. - 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
in table
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>
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. On screen, this is icon ”>>”
FrameLayout @+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>
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.