diff --git a/docs/developer-guide/.vale.ini b/docs/developer-guide/.vale.ini index 8b8cd0f341..695d637a2e 100644 --- a/docs/developer-guide/.vale.ini +++ b/docs/developer-guide/.vale.ini @@ -1,6 +1,34 @@ StylesPath = styles MinAlertLevel = suggestion +Vocab = CodenameOne Packages = https://github.com/errata-ai/packages/releases/download/v0.2.0/Microsoft.zip, https://github.com/errata-ai/packages/releases/download/v0.2.0/proselint.zip, https://github.com/errata-ai/packages/releases/download/v0.2.0/write-good.zip +# How to suppress vale on a specific line +# ───────────────────────────────────────────────────────────────────────────── +# Place an asciidoc comment of the form: +# +# // vale-skip: : +# The next line of content that vale will ignore. +# +# Example: +# +# // vale-skip: Microsoft.ComplexWords: 'Approximate' is the correct technical +# // term here; substituting 'about' loses precision. +# |Approximate fade duration is `durationMs ~= (255 / speed)`... +# +# Notes: +# - The comment must appear on the line immediately preceding the content. It +# protects exactly the next non-empty line. +# - Asciidoctor strips // comments from rendered HTML and PDF output, so the +# comment never appears to readers — only in the source as documentation. +# - The TokenIgnores pattern below matches against asciidoc source, so it works +# inside paragraph text, table cells, list items, and admonition blocks. +# - This skips ALL vale rules for the protected line. Specify the rule name +# and reason in the comment so future maintainers understand the exception. +# - For longer/multi-line exceptions, place a // vale-skip comment before each +# line that needs protection. +# ───────────────────────────────────────────────────────────────────────────── + [*.{adoc,asciidoc}] BasedOnStyles = Microsoft, proselint, write-good +TokenIgnores = (?s)// vale-skip:[^\n]*\n[^\n]+\n diff --git a/docs/developer-guide/Advanced-Theming.asciidoc b/docs/developer-guide/Advanced-Theming.asciidoc index 396b04d01e..8b97b63488 100644 --- a/docs/developer-guide/Advanced-Theming.asciidoc +++ b/docs/developer-guide/Advanced-Theming.asciidoc @@ -27,7 +27,7 @@ Theme layering helps in two common cases: - A platform needs a slightly different theme. - A specific use case needs theme customization. For example, a user might select larger fonts. -Theme layering is straightforward and doesn't require rebuilding the entire theme. A theme can be applied on top of another theme, similar to CSS cascading. To create a layer, add a new theme with the #Add Theme# button. +Theme layering is straightforward and doesn't require rebuilding the entire theme. A theme can be applied on top of another theme, like CSS cascading. To create a layer, add a new theme with the #Add Theme# button. IMPORTANT: Remove the `includeNativeBool` constant from the new theme. @@ -47,7 +47,7 @@ Update it to look like this: include::../demos/common/src/main/java/com/codenameone/developerguide/advancedtheming/AdvancedThemingSnippets.java[tag=initNamedTheme,indent=0] ---- -NOTE: This example assumes that the main theme is named "Theme", not the layer theme that was added. +NOTE: This example assumes that the main theme is named `Theme`, not the layer theme that was added. The original code relies on the theme being at position 0 in the theme name array. That might not always be true. @@ -60,11 +60,11 @@ include::../demos/common/src/main/java/com/codenameone/developerguide/advancedth The `addThemeProps` call places the secondary theme on top of the primary "Theme" and keeps the original UIIDs in "Theme" intact. -To apply theme changes to a running application, call `Form`'s `refreshTheme()`. This updates the UI immediately and provides visual feedback for the theme changes. +To apply theme changes to a running application, call `Form`'s `refreshTheme()`. This updates the UI and provides visual feedback for the theme changes. === Override resources in platform -When you adapt an application to different OS conventions, one common requirement is to use different icons. Sometimes you also need to change behavior based on device type, for example to use a different UI structure for a tablet. +When you adapt an application to different OS conventions, one common need is to use different icons. Sometimes you also need to change behavior based on device type, for example to use a different UI structure for a tablet. Codename One lets you override a resource for a specific platform. You can redefine a resource for that platform and add platform-specific resources. @@ -118,7 +118,7 @@ Internally, Codename One has many built-in constants, and the list keeps growing |`true` to activate the caps text mode in the buttons. When activated `setText` on `Button` and all the constructors will invoke `upcase()` on all the strings effectively making the application buttons use uppercase exclusively. This is `false` by default except for Android where it defaults to `true`. it's equivalent to `Button.setCapsTextDefault(boolean)` and can be tuned to an individual `Component` via `Component.setRippleEffect(boolean)` |capsButtonUiids -|A list of the UIID's that should be capitalized by default (in supported platforms) other than the `Button` and `RaisedButton` which are already capitalized. This list can be separated by spaces or commas e.g. `capsButtonUiids=UpcaseButton,OtherCustomButton` +|A list of the UIID's that should be capitalized by default (in supported platforms) other than the `Button` and `RaisedButton` which are already capitalized. This list can be separated by spaces or commas for example, `capsButtonUiids=UpcaseButton,OtherCustomButton` |defaultCommandImage |Image to give a command with no icon @@ -130,13 +130,13 @@ Internally, Codename One has many built-in constants, and the list keeps growing |Sets the default Gaussian blur radius for the background of the dialogs. The default value is -1 indicating no blur |dialogPosition -|Place the dialog in an arbitrary border layout position (e.g. North, South, Center, etc.) +|Place the dialog in an arbitrary border layout position (for example, North, South, Center, etc.) |centeredPopupBool |Popup of the combo box will appear in the center of the screen |changeTabOnFocusBool -|Useful for feature phones, allows changing the tab when the focus changes immediately, without pressing a key +|Useful for feature phones, allows changing the tab when the focus changes, without pressing a key |checkBoxCheckDisImage |CheckBox image to use instead of Codename One drawing it on its own @@ -185,7 +185,7 @@ SoftKey, Touch, Bar, Title, Right, Native |Allows theming the icon component used by `SpanLabel`, `SpanButton`, `MultiButton`, and `SpanMultiButton` when they fetch icon styling via theme constants. |iosScrollMotionBool -|Enables iOS-style scroll physics (nonlinear rubber-band during drag and critically-damped tensile snap-back). When unset it defaults to `true` on the iOS platform and `false` elsewhere. Set explicitly to override the platform default. See <> +|Enables iOS-style scroll physics (nonlinear rubber-band during drag and critically damped tensile snap-back). When unset it defaults to `true` on the iOS platform and `false` elsewhere. Set explicitly to override the platform default. See <> |imageviewerNavigationArrowsBool |Enables side navigation arrows by default for `ImageViewer` instances using the `ImageViewer` UIID (or a lowercased custom UIID prefix). @@ -215,6 +215,7 @@ SoftKey, Touch, Bar, Title, Right, Native |The UIID used for dialog button commands |dlgCommandButtonSizeInt +// vale-skip: write-good.TooWordy — 'Minimum' is the precise term for a numeric lower bound; 'least' would imply a comparison rather than a constraint. |Minimum size to give to command buttons in the dialog |dlgCommandGridBool @@ -239,10 +240,11 @@ SoftKey, Touch, Bar, Title, Right, Native |Boolean indicating if the scrollbar should fade when there is inactivity |setFadeScrollBarSpeed(int) _(API)_ +// vale-skip: Microsoft.ComplexWords — 'Approximate' is the correct technical term for an estimated duration; 'about' would lose precision in a numeric formula context. |Sets the per-frame opacity decrement for scrollbar fading (not milliseconds). Approximate fade duration is `durationMs ~= (255 / speed) * (1000 / frameRate)`. At 60 FPS: speed `5` ~= `850ms`, `3` ~= `1400ms`, `2` ~= `2100ms`, `1` ~= `4250ms` |fadeScrollEdgeBool -|Places a fade effect at the edges of the screen to indicate that it's possible to scroll until we reach the edge (common on Android) +|Places a fade effect at the edges of the screen to show that it's possible to scroll until you reach the edge (common on Android) |fadeScrollEdgeInt |Amount of pixels to fade out at the edge @@ -272,7 +274,7 @@ SoftKey, Touch, Bar, Title, Right, Native |Indicates that the Toolbar API should be on/off by default for all forms |hasRaisedButtonBool -|Is true in platforms where the theme has the `RaisedButton` UIID defined. This is currently only true in the native Android theme to allow some material design guidelines +|Is true in platforms where the theme has the `RaisedButton` UIID defined. This is only true in the native Android theme to allow some material design guidelines |hideBackCommandBool |Hides the back command from the side menu when possible @@ -350,7 +352,7 @@ SoftKey, Touch, Bar, Title, Right, Native |Allows positioning and sizing the menu |menuImage -|The three dot menu image used in Android and the https://www.codenameone.com/javadoc/com/codename1/ui/Toolbar.html[Toolbar] to show additional command entries +|The three dot menu image used in Android and the https://www.codenameone.com/javadoc/com/codename1/ui/Toolbar.html[Toolbar] to show more command entries |menuImageSize |The size in millimeters (floating point value) of the generated side menu image, this is only used if you don't supply a custom image. The default value is 4.5. @@ -482,7 +484,7 @@ SoftKey, Touch, Bar, Title, Right, Native |Resistance coefficient for the iOS-style rubber-band compression used while the finger drags past a scroll edge. Value is interpreted as hundredths, so `55` means `0.55` (the iOS reference value). Larger values let the content be pulled farther for the same finger travel; smaller values make the content feel stiffer. Only active when `iosScrollMotionBool` is `true`. See <> |ScrollMotion -|Selects the post-release flick model. `DECAY` (default) uses exponential velocity decay controlled by `ScrollMotionTimeConstantInt` and `DecayMotionScaleFactorInt`. Any other value (e.g. `FRICTION`) falls back to the linear-friction model. See <> +|Selects the post-release flick model. `DECAY` (default) uses exponential velocity decay controlled by `ScrollMotionTimeConstantInt` and `DecayMotionScaleFactorInt`. Any other value (for example, `FRICTION`) falls back to the linear-friction model. See <> |ScrollMotionTimeConstantInt |Time constant in milliseconds for the exponential-decay flick model (active when `ScrollMotion=DECAY`). Default is `500`. Smaller values stop the flick sooner @@ -491,7 +493,7 @@ SoftKey, Touch, Bar, Title, Right, Native |`true`/`false` default is platform dependent. Toggles whether the scroll bar is visible |showBackCommandOnTitleBool -|Used by the https://www.codenameone.com/javadoc/com/codename1/ui/Toolbar.html[Toolbar] API to indicate whether the back button should appear on the title +|Used by the https://www.codenameone.com/javadoc/com/codename1/ui/Toolbar.html[Toolbar] API to show whether the back button should appear on the title |shrinkPopupTitleBool |Indicates the title of the popup should be set to 0 if it's missing @@ -590,7 +592,7 @@ SoftKey, Touch, Bar, Title, Right, Native |The placement of the tabs in the https://www.codenameone.com/javadoc/com/codename1/ui/Tabs.html[Tabs] component: TOP = 0, LEFT = 1, BOTTOM = 2, RIGHT = 3 |tabsSlideSpeedInt -|The time of the animation that occurs (in milliseconds) between between releasing a swiped tab and reaching the next tab. Currently defaults to 200 +|The time of the animation that occurs (in milliseconds) between releasing a swiped tab and reaching the next tab. Defaults to 200 |tabsFillRowsBool |Indicates if the tabs should fill the row using flow layout @@ -608,13 +610,13 @@ SoftKey, Touch, Bar, Title, Right, Native |A hex RGB color which defaults to null in which case this has no effect. When defined this will change the color of the border and label to the given color to match the material design styling. This implements the red border underline in cases of error and the label text color change |textComponentOnTopBool -|Toggles the on top mode which makes things look like they do on Android. This defaults to true on Android and false on other OS's. This can also be manipulated via the `onTopMode(boolean)` method in `InputComponent` however the layout will only use the theme constant +|Toggles the on top mode which makes things look like they do on Android. This defaults to true on Android and false on other OS's. This can also be manipulated via the `onTopMode(boolean)` method in `InputComponent`, but the layout will only use the theme constant |textComponentAnimBool |toggles the animation mode which again can be manipulated by a method in `InputComponent`. If you want to keep the UI static without the floating hint effect set this to false. Notice this defaults to true only on Android |textComponentFieldUIID -|sets the UIID of the text field to something other than `TextField` this is useful for platforms such as iOS where the look of the text field is different within the text component. This allows us to make the background of the text field transparent when it's within the `TextComponent` and make it different from the regular text field +|sets the UIID of the text field to something other than `TextField` this is useful for platforms such as iOS where the look of the text field is different within the text component. This allows you to make the background of the text field transparent when it's within the `TextComponent` and make it different from the regular text field |textFieldCursorColorInt |The color of the cursor as an integer (not hex) @@ -656,13 +658,14 @@ SoftKey, Touch, Bar, Title, Right, Native |Picture of a file node for the `Tree` class |tensileDragBool -|Indicates that tensile drag should be enabled/disabled. This is usually set by platform themes +|Indicates that tensile drag should be enabled/disabled. This is set by platform themes |tensileSnapMinDurationInt +// vale-skip: write-good.TooWordy — 'Minimum' is the precise term for the numeric lower bound on duration. |Minimum duration in milliseconds for the tensile snap-back animation when the finger is released past a scroll edge. Defaults to `400` when `iosScrollMotionBool` is `true` and `300` otherwise. The actual duration scales with the distance being snapped; this is only a floor |tensileSnapMotion -|Motion curve used for the tensile snap-back. `SPRING` (the default when `iosScrollMotionBool` is `true`) applies a critically-damped spring envelope that settles softly. `DECELERATION` (the default elsewhere) applies the legacy quadratic ease-out. See <> +|Motion curve used for the tensile snap-back. `SPRING` (the default when `iosScrollMotionBool` is `true`) applies a critically damped spring envelope that settles softly. `DECELERATION` (the default elsewhere) applies the legacy quadratic ease-out. See <> |=== [[scroll-motion-tuning-section]] @@ -670,9 +673,9 @@ SoftKey, Touch, Bar, Title, Right, Native Scroll feel is controlled by three stages, each tunable via theme constants: -. *While the finger is dragging past an edge* -- when `iosScrollMotionBool` is `true`, the over-edge distance is compressed by the iOS UIScrollView rubber-band function `c * d * dim / (c * d + dim)`, where `d` is the raw finger displacement past the edge, `dim` is the viewport dimension, and `c` is `rubberBandCoefficientInt / 100` (default `0.55`). The result is that the content feels heavier the farther you pull it, asymptotically approaching `dim`. When `iosScrollMotionBool` is `false`, the legacy behavior is used: a linear 1:1 follow clamped to `tensileLength`. -. *After the finger is lifted, while there is still velocity* -- the `ScrollMotion` constant selects the model. `DECAY` (default) uses exponential velocity decay with a time constant of `ScrollMotionTimeConstantInt` milliseconds and a distance scale of `DecayMotionScaleFactorInt`. Any other value (e.g. `FRICTION`) falls back to linear friction. -. *When the scroll comes to rest past an edge* -- a tensile snap-back animates the content back to the edge. `tensileSnapMotion` picks the curve: `SPRING` is a critically-damped envelope (iOS-like soft settle) and `DECELERATION` is the legacy quadratic ease-out. The duration scales with distance but never falls below `tensileSnapMinDurationInt` milliseconds. +. *While the finger is dragging past an edge*--when `iosScrollMotionBool` is `true`, the over-edge distance is compressed by the iOS UIScrollView rubber-band function `c * d * dim / (c * d + dim)`, where `d` is the raw finger displacement past the edge, `dim` is the viewport dimension, and `c` is `rubberBandCoefficientInt / 100` (default `0.55`). The result is that the content feels heavier the farther you pull it, asymptotically approaching `dim`. When `iosScrollMotionBool` is `false`, the legacy behavior is used: a linear 1:1 follow clamped to `tensileLength`. +. *After the finger is lifted, while there is still velocity*--the `ScrollMotion` constant selects the model. `DECAY` (default) uses exponential velocity decay with a time constant of `ScrollMotionTimeConstantInt` milliseconds and a distance scale of `DecayMotionScaleFactorInt`. Any other value (for example, `FRICTION`) falls back to linear friction. +. *When the scroll comes to rest past an edge*--a tensile snap-back animates the content back to the edge. `tensileSnapMotion` picks the curve: `SPRING` is a critically damped envelope (iOS-like soft settle) and `DECELERATION` is the legacy quadratic ease-out. The duration scales with distance but never falls below `tensileSnapMinDurationInt` milliseconds. + `iosScrollMotionBool` is the single switch that flips stage 1 and the default of stage 3 to the iOS-matching behavior. It defaults to `true` on the iOS platform only. Every individual constant can still be overridden regardless of this switch, so you can, for example, enable the nonlinear rubber-band globally (`iosScrollMotionBool=true`) while keeping the legacy quadratic snap-back (`tensileSnapMotion=DECELERATION`). @@ -686,16 +689,16 @@ Scroll feel is controlled by three stages, each tunable via theme constants: **** Once a theme constant is set by a theme, it isn't removed on a refresh when replacing the theme. -E.g. if one would set the `comboImage` constant to a specific value in theme A and then switch to theme B, that doesn't define the `comboImage`, the original theme A `comboImage` might remain! +For example, if one would set the `comboImage` constant to a specific value in theme A and then switch to theme B, that doesn't define the `comboImage`, the original theme A `comboImage` might remain! The reason for this is simple: when extracting the constant values, components keep the values in cache locally and just don't track the change in value. Furthermore, since the components allow manually setting values, it's impractical for them to track whether a value was set by a constant or explicitly by the user. -The solution for this is to either manually reset undesired values before replacing a theme (e.g. for the case, above by calling the default look and feel method for setting the combo image with a null value), or defining a constant value to replace the existing value. +The solution for this is to either manually reset undesired values before replacing a theme (for example, for the case, above by calling the default look and feel method for setting the combo image with a null value), or defining a constant value to replace the existing value. **** -=== Native Theming +=== Native theming -Codename One uses a theme constant called `includeNativeBool`, when that constant is set to `true` Codename One starts by loading the native theme first and then applying all the theme settings. This means your theme "derives" the style of the native theme first, similar to the cascading effect of CSS. Internally this is exactly what the <> section covered. +Codename One uses a theme constant called `includeNativeBool`, when that constant is set to `true` Codename One starts by loading the native theme first and then applying all the theme settings. This means your theme "derives" the style of the native theme first, like the cascading effect of CSS. Internally this is what the <> section covered. By avoiding this flag you can create themes that look _EXACTLY_ the same on all platforms. @@ -709,7 +712,7 @@ image::img/designer-native-theme-menu.png[The native theme menu option,scaledwid Developers can pick the platform of their liking and see how the theme will appear in that particular platform by selecting it and having the preview update on the fly. -=== Under the Hood of the Theme Engine +=== Under the hood of the theme engine To truly understand a theme you need to understand what it's. Internally a theme is a `Hashtable` key/value pair between UIID based keys and their respective values. For example: the key: @@ -748,11 +751,11 @@ The type is omitted for the default unselected type, and may be one of _sel_ (se * `backgroundType` - a `Byte` object containing one of the constants for the background type defined in https://www.codenameone.com/javadoc/com/codename1/ui/plaf/Style.html[Style] under BACKGROUND_*. * `backgroundGradient` - contains an `Object` array containing 2 integers for the colors of the gradient. If the gradient is radial it contains 3 floating points defining the x, y & size of the gradient. -So to set the foreground color of a selected button to red, a theme will define a property like: +to set the foreground color of a selected button to red, a theme will define a property like: `Button.sel#fgColor=ff0000` -This information is mostly useful for understanding how things work within Codename One, but it can also be useful in runtime. +This information is useful for understanding how things work within Codename One, but it can also be useful in runtime. For example: to increase the size of all fonts in the application, you can do something like: @@ -762,7 +765,7 @@ include::../demos/common/src/main/java/com/codenameone/developerguide/advancedth ---- [[understanding-images-and-multi-images]] -=== Understanding Images and Multi-Images +=== Understanding images and Multi-Images // HTML_ONLY_START NOTE: This section provides a high-level overview of images. You dive deeper into the various types of images in the https://www.codenameone.com/manual/graphics.html#deep-into-images-section[graphics section]. @@ -774,27 +777,27 @@ NOTE: This section provides a high-level overview of images. You dive deeper int When working with a theme, you often use images for borders or backgrounds. You also use images within the GUI for various purposes and most such images will be extracted from the resource file. -Adding a standard JPEG/PNG image to the resource file is straight forward, and the resulting image can be viewed within the images section. For example, due to the wide difference between device types, an image that would be appropriate in size for an iPhone 3gs would not be appropriate in size for a Nexus device or an iPhone 4 (but perhaps, surprisingly, it will be right for iPad 1 and iPad 2). +Adding a standard JPEG/PNG image to the resource file is straight forward, and the resulting image can be viewed within the images section. For example, due to the wide difference between device types, an image that would be appropriate in size for an iPhone 3gs would not be appropriate in size for a Nexus device or an iPhone 4 (but perhaps, it will be right for iPad 1 and iPad 2). -The density of the devices varies significantly and Codename One tries to simplify the process by unifying everything into one set of values to indicate density. For simplicity's sake, density is sometimes expressed in terms of pixels, however it's mapped internally to actual screen measurements where possible. +The density of the devices varies and Codename One tries to simplify the process by unifying everything into one set of values to show density. For simplicity's sake, density is sometimes expressed in pixels, but it's mapped internally to actual screen measurements where possible. A multi-image is an image that has many varieties for different densities, and thus looks sharp in all the densities. Since scaling on the device can’t interpolate the data (due to performance considerations), significant scaling on the device becomes impractical. For example, a multi-image will provide the "right" resolution image for the given device type. -From the programming perspective this is mostly seamless, a developer accesses one image and has no ability to access the images in the different resolutions. Within the designer, however, you can explicitly define images for many resolutions and perform high quality scaling so the "right" image is available. +From the programming perspective this is seamless, a developer accesses one image and has no ability to access the images in the different resolutions. Within the designer, but, you can explicitly define images for many resolutions and perform high quality scaling so the "right" image is available. You can use two basic methods to add a multi-image: quick add and standard add. Both methods rely on understanding the source resolution of the image, for example: if you've an icon that you expect to be -128x128 pixels on iPhone 4, 102x102 on nexus one and 64x64 on iPhone 3gs. You can provide the source image +128×128 pixels on iPhone 4, 102×102 on nexus one and 64×64 on iPhone 3gs. You can provide the source image as the 128 pixel image and perform a quick add option while picking the # High# density option. -This will indicate to the algorithm that your source image is designed for the " high" density and it will scale for +This will show to the algorithm that your source image is designed for the " high" density and it will scale for the rest of the densities accordingly. TIP: This relies on the common use case of asking your designer to design for one high end device (for example: iPhone X) then you can take the resources and add them as "HD" resources. They will automatically adapt to the lower resolutions -Alternatively, you can use the standard add multi-image dialog and set it like this: +Or, you can use the standard add multi-image dialog and set it like this: image::img/select-image-resolutions.png[Multi-image resolution dialog,scaledwidth=50%] @@ -804,9 +807,10 @@ on the closest alternative. The percentage value will change the entire column, and it means the percentage of the screen. For example: You know the icon is 128 for the high resolution, you can move the percentage until you reach something close to -128 in the " High" row and the other rows will represent a size that should be pretty close in terms of physical +128 in the " High" row and the other rows will represent a size that should be pretty close in physical size to the 128 figure. +// vale-skip: Microsoft.ComplexWords — 'approximate' is correct here (an estimate of pixel density); 'about pixel density' would be wrong English. At runtime, you can always find the host device's approximate pixel density using the `Display.getDeviceDensity()` method. This will return one of: @@ -824,17 +828,18 @@ method. This will return one of: | `Density.DENSITY_4K` | ~ 1250ppi | |===== -=== Use Millimeters for Padding/Margin and Font Sizes +=== Use millimeters for Padding/Margin and font sizes -When configuring your styles, you should almost never use "Pixels" as the unit for padding, margins, font size, +// vale-skip: write-good.TooWordy — 'rarely' is the natural word here; 'seldom' (vale's preferred substitute) reads as archaic in modern technical writing. +When configuring your styles, you should rarely use "Pixels" as the unit for padding, margins, font size, and border thickness because the results will be inconsistent on different densities. Instead, you should use millimeters for all non-zero units of measurement. As you now understand the <> it should be clear why this is important. -==== Fractions of Millimeters +==== Fractions of millimeters -Sometimes millimeters don't give you enough precision for what you want to do. Currently the designer allows you to specify integer values for most units. For example, you can achieve more precise results when working directly in Java. The `Display.convertToPixels()` method will allow you to convert millimeters (or DIPS) to pixels. It also takes an integer input, but you can use it to obtain a multiplier that you can then use to convert any millimeter value you want into pixels. +Sometimes millimeters don't give you enough precision for what you want to do. The designer allows you to specify integer values for most units. For example, you can achieve more precise results when working directly in Java. The `Display.convertToPixels()` method will allow you to convert millimeters (or DIPS) to pixels. It also takes an integer input, but you can use it to get a multiplier that you can then use to convert any millimeter value you want into pixels. For example: @@ -850,7 +855,7 @@ And now you can set the padding on an element to 1.5mm. For example: include::../demos/common/src/main/java/com/codenameone/developerguide/advancedtheming/AdvancedThemingSnippets.java[tag=paddingFromMM,indent=0] ---- -=== Creating a Great Looking Side Menu +=== Creating a great looking side menu .Side Menu final result image::img/styled-sidemenu-result.png[Side Menu final result,scaledwidth=50%] @@ -867,7 +872,7 @@ include::../demos/common/src/main/java/com/codenameone/developerguide/advancedth ---- -<1> This is the icon which was used in lieu of a logo it appears in the top right of the side menu +<1> This is the icon which was used instead of a logo it appears in the top right of the side menu <2> This is the top bar containing the tagline and the icon it's styled as if it's a command but you can put anything here for example: an image etc. <3> The commands are added as usual to the side menu with no styling or functionality, the entire look is determined by the theme @@ -903,7 +908,7 @@ The iOS version of the side command inherits a border style so you must "remove" .Border must be defined as Empty image::img/styled-sidemenu-6.png[Border must be defined as Empty,scaledwidth=40%] -Next you need to pick a good looking font and make sure it's large enough. You use millimeters size it correctly for all OS’s and override the derived text decoration which has a value in the iOS native theme so it can impact the final look. +Next you need to pick a good looking font and make sure it's large enough. You use millimeters size it for all OS’s and override the derived text decoration which has a value in the iOS native theme so it can impact the final look. .Pick a good looking font for the side command image::img/styled-sidemenu-7.png[Pick a good looking font for the side command,scaledwidth=40%] @@ -931,13 +936,13 @@ The last change for the theme is for the #StatusBarSideMenu# UIID which is a spa .StatusBarSideMenu padding for the top of the side menu image::img/styled-sidemenu-11.png[StatusBarSideMenu padding for the top of the side menu,scaledwidth=40%] -Finally, you will add the icon image (or a logo if you've it) into the theme as a multi image so you can use it within the side menu as a good looking logo. A relatively large icon image works as a 2HD multi-image but you can use many strategies to get a fitting image for this spot. +Finally, you will add the icon image (or a logo if you've it) into the theme as a multi image so you can use it within the side menu as a good looking logo. A large icon image works as a 2HD multi-image but you can use many strategies to get a fitting image for this spot. TIP: Rounded images work well here, you can round images dynamically using masking These steps produce the UI above as a side menu, they might seem like a long set of steps but each step is pretty simple as you walk thru each one. This does show off the versatility and power of Codename One as a change to one step can create a radically different UI design. -=== Converting a PSD To A Theme +=== Converting a PSD to a theme Codename One provides extensive support for designing beautiful user interfaces, but it isn't necessarily obvious to new developers how to achieve their desired results. A common workflow for app design includes a PSD file with mock-ups of the UI, created by a professional designer. @@ -968,7 +973,7 @@ Here is a screenshot of the resulting component running inside the Codename One .Resulting app in the Codename One simulator image::img/psd2app-image3.png[Resulting app in the Codename One simulator,scaledwidth=25%] -==== Breaking Down the PSD +==== Breaking down the PSD Open the PSD you're interested in using Photoshop. @@ -978,7 +983,7 @@ In this PSD you want one of the screen designs so initially you want to remove e - Select the drag tool (the top left tool) - In the toolbar for the tool (top bar area) check the #Auto Select# mode -- Select the #Layer Mode# for auto selection (in some cases group would actually be better so feel free to experiment) +- Select the #Layer Mode# for auto selection (sometimes group would actually be better so feel free to experiment) - Click on the portion in the PSD that you're interested in You should end up with something like this where a layer is selected in the layers window: @@ -1003,11 +1008,11 @@ Once the layer hierarchy is a smart object you can double click it which will op .Double clicking the smart object allows you to edit the form you need image::img/psd2app-image7.png[Double clicking the smart object allows you to edit the form you need,scaledwidth=40%] -===== Removing the Noise +===== Removing the noise -The first thing you need to do is remove from the image all of the things that you don't need. The status bar area on the top is redundant as if is a part of the phones UI. You can select it using the select tool and click the eye icon next to the layer to hide it. +The first thing you need to do is remove from the image all the things that you don't need. The status bar area on the top is redundant as if is a part of the phones UI. You can select it using the select tool and click the eye icon next to the layer to hide it. -Normally you'd want to have the back arrow but thanks to the material design icons that are a part of Codename One you don't need that icon so you can hide that too. +You'd want to have the back arrow but thanks to the material design icons that are a part of Codename One you don't need that icon so you can hide that too. You don't need the "Sign Up" or "Done" strings in the title either but before removing them you'd like to know the font that's used. @@ -1030,17 +1035,17 @@ image::img/psd2app-image10.png[The color dialog lists the hex color at the botto You can now hide both text layers so they won't pose a problem later. -===== The Camera Button +===== The camera Button -The camera button includes an icon and the button background itself. You can use that as a single image and be done with it, but for the purpose of this tutorial I will take the harder route of separating this into a button background and a foreground image. +The camera button includes an icon and the button background itself. You can use that as a single image and be done with it, but for this tutorial I will take the harder route of separating this into a button background and a foreground image. When you click on the camera icon you will notice that the camera icon is comprised of two separate layers: the camera and the "x" symbol above it. You can select both layers using #ctrl-click# (command click on the Mac) and convert both to a smart object together using the same method as before: .The camera smart object image::img/psd2app-image11.png[The camera smart object] -Since the image is used as an icon you want it to be completely square which isn't the situation here! + -This is important as a non-square image can trigger misalignment when dealing with icons and the background. So you need to use the #Image# -> #Canvas Size# menu and set the values to be the same (the higher value of the two). +Since the image is used as an icon you want it to be square which isn't the situation here! + +This is important as a non-square image can trigger misalignment when dealing with icons and the background. you need to use the #Image# -> #Canvas Size# menu and set the values to be the same (the higher value of the two). .The canvas size dialog for the camera.png file image::img/psd-image-size.png[The canvas size dialog for the camera.png file,scaledwidth=40%] @@ -1063,7 +1068,7 @@ image::img/camera-button.png[The camera button image set to a gray background so Now you can hide both of these elements and proceed to get the background image for the title. -Here the "smart object trick" won't work... There is an effects layer in place and the smart object will provide you with the real underlying image instead of the look you actually want. For example, solving this is trivial now that you hid all of the elements on top of the image! +Here the "smart object trick" won't work... There is an effects layer in place and the smart object will provide you with the real underlying image instead of the look you actually want. For example, solving this is trivial now that you hid all the elements on top of the image! You need to switch to the rectangular select tool: @@ -1072,7 +1077,7 @@ image::img/psd2app-image12.png[The select tool and the clean image you want to s Now drag the select tool to select the image don't cross into the white pixels below the image. You can use the zoom value and set it to a high value to get the selection right. -When the selection is right click #Edit# -> #Copy Merged#. Normally #Copy# would copy a specific layer but in this case you want to copy what you see on the screen! +When the selection is right click #Edit# -> #Copy Merged#. #Copy# would copy a specific layer but in this case you want to copy what you see on the screen! Now click #File# -> #New# it should have the #Presets# set to #Clipboard# which means the newly created image is based on what you copied (that's seriously great UX). Just accept that dialog and paste (#Ctrl-V# or #Command-V#). @@ -1088,9 +1093,9 @@ image::img/psd2app-image13.png[The eye drop tool can be pointed at an area of th ==== The Code -While that was verbose it was relatively simple. You will create a simple barebones manual application with the native theme. +While that was verbose it was simple. You will create a simple barebones manual application with the native theme. -NOTE: The reason for this is to avoid "noise", if you use a more elaborate theme it would have some existing settings. This can make the tutorial harder to follow +NOTE: The reason for this is to avoid "noise," if you use a more elaborate theme it would have some existing settings. This can make the tutorial harder to follow .Simple bare bones app settings image::img/psd2app-image14.png[Simple bare bones app settings] @@ -1099,7 +1104,7 @@ Once the project is created double click the `theme.res` file and within the des Then save the resource file so you can use these images from code. -Here is the source code you used to work with the UI above there are comments within the code explaining some of the logic: +Here is the source code you used to work with the UI above there are comments within the code explaining some logic: [source,java] ---- @@ -1111,17 +1116,17 @@ include::../demos/common/src/main/java/com/codenameone/developerguide/advancedth include::../demos/common/src/main/java/com/codenameone/developerguide/advancedtheming/PsdTutorialDemo.java[tag=psdStart,indent=0] ---- -==== Styling The UI +==== Styling the UI -So the code above is most of the work but you still need to put everything together using the theme. This is what you've so far: +the code above is most of the work but you still need to put everything together using the theme. This is what you've so far: .Before applying the changes to the theme this is what you've image::img/psd2app-image15.png[Before applying the changes to the theme this is what you've,scaledwidth=20%] -.This is what you're aiming at with no additional code changes -image::img/psd2app-image16.png[This is what you're aiming at with no additional code changes,scaledwidth=20%] +.This is what you're aiming at with no more code changes +image::img/psd2app-image16.png[This is what you're aiming at with no more code changes,scaledwidth=20%] -This looks like a major set of changes but it requires exactly 10 UIID definitions to get to this look! +This looks like a major set of changes but it requires 10 UIID definitions to get to this look! Open the designer and select the theme. Press the #Add# button and type in #TitleContainer#. Uncheck derive for the background and select #IMAGE_SCALED_FILL# for the #Type# and the #background.jpg# image. @@ -1144,7 +1149,7 @@ Press #OK# to save the changes. Copy the `Title` UIID and paste it, change the name to "TitleCommand" and press #OK# to save the changes. -Copy the `Title` UIID again and paste it, change the name to "RedCommand". In the #Color# tab set the foreground color to `f73267`. In the #Font# tab set the #True Type# to #native:MainLight# and set the size to 3. Press #OK# to save the changes. +Copy the `Title` UIID again and paste it, change the name to `RedCommand`. In the #Color# tab set the foreground color to `f73267`. In the #Font# tab set the #True Type# to #native:MainLight# and set the size to 3. Press #OK# to save the changes. Add the "TitleArea" UIID. In the #Color# tab define transparency as `0` (fully transparent so you will see the `TitleContainer`). Define padding and margin as 0 on all sides. + In the #Border# tab press the #...# button and select #[Empty]#. @@ -1154,7 +1159,7 @@ Add the "TextField" UIID. In the #Color# tab define transparency as `255` (fully In the #Border# tab press the #...# button and select #[Empty]#. In the #Font# tab set the #True Type# to #native:MainLight# and set the size to 2. Press #OK# to save the changes. -Copy the `TextField` UIID again and paste it, change the name to "TextHint". In the #Color# tab set the foreground color to `4d606f`. Press #OK# to save the changes. +Copy the `TextField` UIID again and paste it, change the name to `TextHint`. In the #Color# tab set the foreground color to `4d606f`. Press #OK# to save the changes. Add the "SouthButton" UIID. In the #Color# tab define transparency as `255` (fully opaque) and the background as `f73267` (red) and the foreground as `ffffff` (white). Define #Alignment# as #Center#. + @@ -1183,8 +1188,8 @@ Press #OK# to save the changes. You can now save the theme and the app should look like the final result! -===== Not Quite There Yet +===== Not quite there yet -There is one last piece that you would notice if you actually try to run this code. When pressing the buttons/text fields you would see their look change completely due to the different styles for focus/press behavior. +There is one last piece that you would notice if you actually try to run this code. When pressing the buttons/text fields you would see their look change due to the different styles for focus/press behavior. You can derive the regular styles from the selected/pressed styles but one of the simplest ways is to copy & paste the styles to the pressed/selected tabs. You can copy `CameraButton`, `RedCommand`, `SouthButton` & `TextField` to the selected state. Then copy `CameraButton`, `RedCommand` & `SouthButton` to the pressed state to get the complete app running! diff --git a/docs/developer-guide/Advanced-Topics-Under-The-Hood.asciidoc b/docs/developer-guide/Advanced-Topics-Under-The-Hood.asciidoc index 69a18f8eb0..e2be5d4afa 100644 --- a/docs/developer-guide/Advanced-Topics-Under-The-Hood.asciidoc +++ b/docs/developer-guide/Advanced-Topics-Under-The-Hood.asciidoc @@ -2,9 +2,9 @@ === Sending arguments to the build server -When you send a build to the server, you can provide additional parameters. The server incorporates those parameters into the build process as build hints. +When you send a build to the server, you can provide more parameters. The server incorporates those parameters into the build process as build hints. -These hints are often called "build hints" or "build arguments". They behave like compiler flags that tune the build server's behavior. That makes them useful for fast iteration on new functionality without rebuilding plugin UI for every change. They are also useful when you need to expose low-level behavior such as customizing the Android manifest XML or the iOS plist. +These hints are often called "build hints" or "build arguments." They behave like compiler flags that tune the build server's behavior. That makes them useful for fast iteration on new functionality without rebuilding plugin UI for every change. They are also useful when you need to expose low-level behavior such as customizing the Android manifest XML or the iOS plist. You can set these hints by right-clicking the project in the IDE and selecting #Codename One# -> #Codename One Settings# -> #Build Hints#. The hints use the `key=value` style. @@ -32,7 +32,7 @@ Here is the current list of supported arguments. Build hints change over time, s |defaults to an empty string. Allows developers of native Android code to add text within the application block to define things such as widgets, services etc. |android.permission.PERMISSION_NAME -|true/false Whether to include a particular permission. Use of these build hints is preferred to `android.xpermissions` since they avoid possible conflicts with libraries. See https://developer.android.com/reference/android/Manifest.permission.html[Android's Manifest.permission docs] for a full list of permissions. +|true/false Whether to include a particular permission. Use of these build hints is preferred to `android.xpermissions` since they avoid possible conflicts with libraries. See https://developer.android.com/reference/android/Manifest.permission.html[Android's Manifest.permission docs] for a full list of permissions. |android.permission.PERMISSION_NAME.maxSdkVersion |Will be translated to the `maxSdkVersion` attribute of the `` tag for the corresponding `android.permission.PERMISSION_NAME` build hint. (Optional) @@ -41,21 +41,21 @@ Here is the current list of supported arguments. Build hints change over time, s |true/false Will be translated to the `required` attribute of the `` tag for the corresponding `android.permission.PERMISSION_NAME` build hint. (Optional) |android.xpermissions -|additional permissions for the Android manifest +|more permissions for the Android manifest |android.xintent_filter |Allows adding an intent filter to the main android activity |android.activity.launchMode -|Allows explicitly setting the `android:launchMode` attribute of the main activity in android. Default is "singleTop", but for some applications you may need to change this behaviour. In particular, apps that are meant to open a file type will need to set this to "singleTask". See https://developer.android.com/guide/topics/manifest/activity-element.html[Android docs for the activity element] for more information about the `android:launchMode` attribute. +|Allows explicitly setting the `android:launchMode` attribute of the main activity in android. Default is "singleTop," but for some applications you may need to change this behaviour. In particular, apps that are meant to open a file type will need to set this to "singleTask." See https://developer.android.com/guide/topics/manifest/activity-element.html[Android docs for the activity element] for more information about the `android:launchMode` attribute. |android.licenseKey -|The license key for the Android app, this is required if you use in-app-purchase on Android +|The license key for the Android app, this is required if you use in-app purchase on Android |android.signingV1 -|true/false Default true. See https://source.android.com/docs/security/features/apksigning +|true/false Default true. See https://source.android.com/docs/security/features/apksigning |android.signingV2 |true/false Default true. See https://source.android.com/docs/security/features/apksigning @@ -82,7 +82,7 @@ Here is the current list of supported arguments. Build hints change over time, s |Device key used to mark a specific Android device as a test device for Google Play ads defaults to C6783E2486F0931D9D09FABC65094FDF |android.includeGPlayServices -|*Deprecated, please android.playService.+++*+++!* Indicates whether Goolge Play Services should be included into the build, defaults to false but that might change based on the functionality of the application and other build hints. Adding Google Play Services support allows us to use a more refined location implementation and invoke some Google specific functionality from native code. +|*Deprecated, please android.playService.+++*+++!* Indicates whether Goolge Play Services should be included into the build, defaults to false but that might change based on the functionality of the application and other build hints. Adding Google Play Services support allows you to use a more refined location implementation and invoke some Google specific functionality from native code. |android.playService.plus, android.playService.auth, android.playService.base, android.playService.identity, android.playService.indexing, android.playService.appInvite, android.playService.analytics, android.playService.cast, android.playService.gcm, android.playService.drive, android.playService.fitness, android.playService.location, android.playService.maps, android.playService.ads, android.playService.vision, android.playService.nearby, android.playService.panorama, android.playService.games, android.playService.safetynet, android.playService.wallet, android.playService.wearable |Allows including only a specific play services library portion. Notice that this setting conflicts with the deprecated `android.includeGPlayServices` and only works with the Gradle-based Android build pipeline. + @@ -90,10 +90,10 @@ Here is the current list of supported arguments. Build hints change over time, s If none of the services are defined to true then plus, auth, base, analytics, gcm, location, maps & ads will be set to true. If one or more of the `android.playService` entries are defined to something then all entries will default to false. |android.playServicesVersion -| The version number of play services to build against. Experimental. **Use with caution** as building against versions other than the server default may introduce incompatibilities with some Codename One APIs. +| The version number of play services to build against. Experimental. **Use with caution** as building against versions other than the server default may introduce incompatibilities with some Codename One APIs. |xxx.minPlayServicesVersion -|This is a special case build hint. You can use any prefix to the build hint and the convention is to use your cn1lib name. it's identical to `android.minPlayServicesVersion` with the exception that the "highest version wins". That way if your cn1lib requires play services 9+ and uses: `myLib.minPlayServicesVersion=9.0.0` and another library has `otherLib.minPlayServicesVersion=10.0.0` then play services will be 10.0.0 +|This is a special case build hint. You can use any prefix to the build hint and the convention is to use your cn1lib name. it's identical to `android.minPlayServicesVersion` with the exception that the "highest version wins." That way if your cn1lib requires play services 9+ and uses: `myLib.minPlayServicesVersion=9.0.0` and another library has `otherLib.minPlayServicesVersion=10.0.0` then play services will be 10.0.0 |android.multidex |Boolean true/false defaults to false. Multidex allows Android binaries to reference more than 65536 methods. This slows builds a bit so you've it off by default but if you get a build error mentioning this limit you should turn this on. @@ -102,19 +102,19 @@ If none of the services are defined to true then plus, auth, base, analytics, gc |Boolean true/false defaults to false. When set to true it assumes the main class has two methods: `headphonesConnected` & `headphonesDisconnected` which it invokes appropriately as needed |android.gpsPermission -|Indicates whether the GPS permission should be requested, it's auto-detected by default if you use the location API. However, some code might want to explicitly define it +|Indicates whether the GPS permission should be requested, it's auto-detected by default if you use the location API. But, some code might want to explicitly define it |android.asyncPaint |Boolean true/false defaults to true. Toggles the Android pipeline between the legacy pipeline (false) and new pipeline (true) |android.stringsXml -|Allows injecting additional entries into the strings.xml file using a value that includes something like this `value1value2` +|Allows injecting more entries into the strings.xml file using a value that includes something like this `value1value2` |android.supportV4 -|Boolean true/false defaults to false but that can change based on usage (e.g. push implicitly activates this). Indicates whether the android support v4 library should be included in the build +|Boolean true/false defaults to false but that can change based on usage (for example, push implicitly activates this). Indicates whether the android support v4 library should be included in the build |android.style -|Allows injecting additional data into the `styles.xml` file right before the closing resources tag +|Allows injecting more data into the `styles.xml` file right before the closing resources tag |android.enableAdaptiveIcons |Boolean true/false defaults to false. Enables Android adaptive icon generation in Android Gradle builds. When enabled, Codename One generates `mipmap` launcher resources (`ic_launcher`, `ic_launcher_foreground`, and adaptive XML in `mipmap-anydpi-v26`) and uses them in the application manifest (`android:icon` and `android:roundIcon`). @@ -126,10 +126,10 @@ If none of the services are defined to true then plus, auth, base, analytics, gc |Optional path (relative to the root of the native Android project) to an image file to use as the adaptive icon background when `android.enableAdaptiveIcons=true`. If this property is set, it overrides `android.adaptiveIconBackground`. |android.cusom_layout1 -|Applies to any number of layouts as long as they are in sequence (e.g. android.cusom_layout2, android.cusom_layout3 etc.). Will write the content of the argument as a layout xml file and give it the name `cusom_layout1.xml` onwards. This can be used by native code to work with XML files +|Applies to any number of layouts as long as they are in sequence (for example, android.cusom_layout2, android.cusom_layout3 etc.). Will write the content of the argument as a layout xml file and give it the name `cusom_layout1.xml` onwards. This can be used by native code to work with XML files |android.keyboardOpen -|Boolean true/false defaults to true. Toggles the new async keyboard mode that leaves the keyboard open while we move between text components +|Boolean true/false defaults to true. Toggles the new async keyboard mode that leaves the keyboard open while you move between text components |android.versionCode |Allows overriding the auto generated version number with a custom internal version number specifically used for the xml attribute `android:versionCode` @@ -138,31 +138,31 @@ If none of the services are defined to true then plus, auth, base, analytics, gc |Indicates whether the `RECORD_AUDIO` permission should be requested. Can be `enabled` or any other value to disable this option |android.nonconsumable -|Comma delimited string of items that are non-consumable in the in-app-purchase API +|Comma delimited string of items that are non-consumable in the in-app purchase API |android.removeBasePermissions -|Boolean true/false defaults to false. Disables the built-in permissions specifically `INTERNET` permission (i.e. no networking...) +|Boolean true/false defaults to false. Disables the built-in permissions specifically `INTERNET` permission (that is, no networking...) |android.blockExternalStoragePermission |Boolean true/false defaults to false. Disables the external storage (SD card) permission |android.min_sdk_version -|The minimum SDK required to run this app, the default value changes based on functionality but can be as low as 7. This corresponds to the XML attribute `android:minSdkVersion`. +|The least SDK required to run this app, the default value changes based on functionality but can be as low as 7. This corresponds to the XML attribute `android:minSdkVersion`. |android.manifest.queries -|Embeds XML content into the section of the Android manifest file. This is https://developer.android.com/training/package-visibility[required in Android 11 for package visibility]. See https://developer.android.com/guide/topics/manifest/queries-element[queries element Android documentation]. +|Embeds XML content into the section of the Android manifest file. This is https://developer.android.com/training/package-visibility[required in Android 11 for package visibility]. See https://developer.android.com/guide/topics/manifest/queries-element[queries element Android documentation]. |android.mockLocation |Boolean true/false defaults to true. Toggles the mock location permission which is on by default, this allows easier debugging of Android device location based services |android.smallScreens -|Boolean true/false defaults to true. Corresponds to the `android:smallScreens` XML attribute and allows disabling the support for very small phones +|Boolean true/false defaults to true. Corresponds to the `android:smallScreens` XML attribute and allows disabling the support for small phones |android.xapplication_attr -|Allows injecting additional attributes into the `application`` tag in the Android XML +|Allows injecting more attributes into the `application`` tag in the Android XML |android.xactivity -|Allows injecting additional attributes into the `activity` tag in the Android XML +|Allows injecting more attributes into the `activity` tag in the Android XML |android.streamMode |The mode in which the volume key should behave, defaults to OS default. Allows setting it to `music` for music playback apps @@ -174,10 +174,10 @@ If none of the services are defined to true then plus, auth, base, analytics, gc |Boolean true/false defaults to true. Allows disabling the proguard obfuscation even on release builds, notice that this isn't recommended |android.proguardKeep -|Arguments for the keep option in proguard allowing us to keep a pattern of files e.g. `-keep class com.mypackage.ProblemClass { *; }` +|Arguments for the keep option in proguard allowing you to keep a pattern of files for example, `-keep class com.mypackage.ProblemClass { *; }` |android.shrinkResources -|Boolean true/false defaults to false. Used only in conjunction with android.enableProguard. Strips out unused resources to reduce apk size. Since 7.0 +|Boolean true/false defaults to false. Used only in conjunction with android.enableProguard. Strips out unused resources to reduce apk size. Since 7.0 |android.sharedUserId |Allows adding a manifest attribute for the sharedUserId option @@ -186,31 +186,31 @@ If none of the services are defined to true then plus, auth, base, analytics, gc |Allows adding a manifest attribute for the sharedUserLabel option |android.targetSDKVersion -|Indicates the Android SDK used to compile the Android build currently defaults to 21. Notice that not all targets will work since the source might have some limitations and not all SDK targets are installed on the build servers. +|Indicates the Android SDK used to compile the Android build defaults to 21. Notice that not all targets will work since the source might have some limitations and not all SDK targets are installed on the build servers. |android.useAndroidX -|Use Android X instead of support libraries. This will also run a find/replace on all source files to replace support libraries and artifacts with AndroidX equivalents. +|Use Android X instead of support libraries. This will also run a find/replace on all source files to replace support libraries and artifacts with AndroidX equivalents. |android.rootCheck -|Boolean true/false defaults to false. Indicates whether the app should check for root access on the device. If root access is detected, the app will exit. +|Boolean true/false defaults to false. Indicates whether the app should check for root access on the device. If root access is detected, the app will exit. |android.fridaDetection -|Boolean true/false defaults to false. Indicates whether the app should check for the presence of the https://www.frida.re/[Frida] dynamic instrumentation toolkit on the device. If Frida is detected, the app will exit. This uses the [frida-blocker](https://github.com/shannah/frida-blocker) library to perform the frida detection. +|Boolean true/false defaults to false. Indicates whether the app should check for the presence of the https://www.frida.re/[Frida] dynamic instrumentation toolkit on the device. If Frida is detected, the app will exit. This uses the [frida-blocker](https://github.com/shannah/frida-blocker) library to perform the frida detection. |android.fridaVersion -|x.y.z The version of [frida-blocker](https://github.com/shannah/frida-blocker) to use to perform frida detection. This is only relevant if `android.fridaDetection=true`. If omitted, it will use the latest tested version in the build server. +|x.y.z The version of [frida-blocker](https://github.com/shannah/frida-blocker) to use to perform frida detection. This is only relevant if `android.fridaDetection=true`. If omitted, it will use the latest tested version in the build server. |android.fridaDebugLogging -|Boolean true/false defaults to false. If true, it will add verbose debug logs during frida detection to show which check if fails on. +|Boolean true/false defaults to false. If true, it will add verbose debug logs during frida detection to show which check if fails on. |android.theme -|Light or Dark defaults to Light. On Android 4+ the default Holo theme is used to render the native widgets in some cases and this indicates whether holo light or holo dark is used. Currently this doesn't affect the Codename One theme but that might change in the future. +|Light or Dark defaults to Light. On Android 4+ the default Holo theme is used to render the native widgets sometimes and this indicates whether holo light or holo dark is used. This doesn't affect the Codename One theme but that might change in the future. |android.web_loading_hidden |true/false defaults to false - set to true to hide the progress indicator that appears when loading a web page on Android. |block_server_registration -|true/false flag defaults to false. By default Codename One applications register with our server, setting this to true blocks them from sending information to our cloud. We keep this data for statistical purposes and intend to provide additional installation stats in the future. +|true/false flag defaults to false. By default Codename One applications register with the Codename One server. Setting this to true blocks them from sending information to the Codename One cloud, which is kept for statistical purposes and may be used to provide more installation stats in the future. |facebook.appId |The application ID for an app that requires native Facebook login integration, this defaults to null which means native Facebook support shouldn't be in the app @@ -222,38 +222,38 @@ If none of the services are defined to true then plus, auth, base, analytics, gc |The Android/chrome push identifier, see the push section for more details | android.background_push_handling -| Deliver push messages on Android when the app is minimized by setting this to "true". Default behaviour is to deliver the message only if the app is in the foreground when received, or after the user taps on the notification to open the app, if the app was in the background when the message was received. +| Deliver push messages on Android when the app is minimized by setting this to "true." Default behaviour is to deliver the message only if the app is in the foreground when received, or after the user taps on the notification to open the app, if the app was in the background when the message was received. | desktop.mac.plist.PLISTKEY -| Set the key `PLISTKEY` in the Info.plist file for desktop mac build. E.g. `desktop.mac.plist.LSApplicationCategoryType=public.app-category.business`. See https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Introduction/Introduction.html[Apple Documentation of Info.plist keys and values for a full list of supported keys]. +| Set the key `PLISTKEY` in the Info.plist file for desktop mac build. For example, `desktop.mac.plist.LSApplicationCategoryType=public.app-category.business`. See https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Introduction/Introduction.html[Apple Documentation of Info.plist keys and values for a full list of supported keys]. + -Currently only supported for App Store builds. See https://www.codenameone.com/developer-guide.html#_mac_os_desktop_build_options[Mac OS Desktop Build Options] for more information. +Only supported for App Store builds. See https://www.codenameone.com/developer-guide.html#_mac_os_desktop_build_options[Mac OS Desktop Build Options] for more information. | desktop.mac.plistInject -| Injects raw XML into the Info.plist file for desktop builds. E.g. `desktop.mac.plistInject=LSApplicationCategoryTypepublic.app-category.business` +| Injects raw XML into the Info.plist file for desktop builds. For example, `desktop.mac.plistInject=LSApplicationCategoryTypepublic.app-category.business` + -Currently only supported for App Store builds. See https://www.codenameone.com/developer-guide.html#_mac_os_desktop_build_options[Mac OS Desktop Build Options] for more information. +Only supported for App Store builds. See https://www.codenameone.com/developer-guide.html#_mac_os_desktop_build_options[Mac OS Desktop Build Options] for more information. |ios.associatedDomains -|Comma-delimited list of domains associated with this app. Since 6.0. Note that each domain should be prefixed by a supported prefix. E.g. "applinks:" or "webcredentials:". See https://developer.apple.com/documentation/security/password_autofill/setting_up_an_app_s_associated_domains?language=objc[Apple's documentation on Associated domains] for more information. +|Comma-delimited list of domains associated with this app. Since 6.0. Note that each domain should be prefixed by a supported prefix. For example, "applinks:" or "webcredentials:." See https://developer.apple.com/documentation/security/password_autofill/setting_up_an_app_s_associated_domains?language=objc[Apple's documentation on Associated domains] for more information. |ios.bitcode |true/false defaults to false. Enables bitcode support for the build. |ios.debug.archs -|Can be set to "armv7" to force iOS debug builds to be 32 bit. By default, debug builds are 64 bit only. +|Can be set to "armv7" to force iOS debug builds to be 32 bit. By default, debug builds are 64 bit only. |ios.release.archs -|Can be set to "arm64" to only build iOS release builds for 64 bit. By default, release builds are both 32 and 64 bit. +|Can be set to "arm64" to only build iOS release builds for 64 bit. By default, release builds are both 32 and 64 bit. |ios.distributionMethod -|Specifies distribution type for debug iOS builds. This is generally used for enterprise or ad-hoc builds (using values "enterprise" and "ad-hoc" respectively). +|Specifies distribution type for debug iOS builds. This is used for enterprise or ad-hoc builds (using values "enterprise" and "ad-hoc" respectively). |ios.debug.distributionMethod -|Specifies distribution type for debug iOS builds only. This is generally used for enterprise or ad-hoc builds (using values "enterprise" and "ad-hoc" respectively). +|Specifies distribution type for debug iOS builds only. This is used for enterprise or ad-hoc builds (using values "enterprise" and "ad-hoc" respectively). |ios.release.distributionMethod -|Specifies distribution type for release iOS builds only. This is generally used for enterprise or ad-hoc builds (using values "enterprise" and "ad-hoc" respectively). +|Specifies distribution type for release iOS builds only. This is used for enterprise or ad-hoc builds (using values "enterprise" and "ad-hoc" respectively). |ios.keyboardOpen |Flips between iOS keyboard open mode and auto-fold keyboard mode. Defaults to true which means the keyboard will remain open and not fold automatically when editing moves to another field. @@ -268,7 +268,7 @@ Currently only supported for App Store builds. See https://www.codenameone.com/ |Use AVKit for video components on iOS rather than `MPMoviePlayerController` on iOS versions 8 through 12. iOS 13 will always use AVKit, and iOS 7 and lower will always use `MPMoviePlayerController`. Default value `false` |ios.teamId -|Specifies the team ID associated with the iOS provisioning profile and certificate. Use `ios.debug.teamId` and `ios.release.teamId` to specify different team IDs for debug and release builds respectively. +|Specifies the team ID associated with the iOS provisioning profile and certificate. Use `ios.debug.teamId` and `ios.release.teamId` to specify different team IDs for debug and release builds respectively. |ios.debug.teamId |Specifies the team ID associated with the iOS debug provisioning profile and certificate. @@ -280,6 +280,7 @@ Currently only supported for App Store builds. See https://www.codenameone.com/ |one of ios, ipad, iphone (defaults to ios). Indicates whether the resulting binary is targeted to the iphone only or ipad only. Notice that the IDE plugin has a "Project Type" combo box you *should* use under the iOS section. |ios.rpmalloc +// vale-skip: write-good.TooWordy — 'minimum' refers to the deployment-target floor; 'least' would change the meaning. |`true`/`false` Use https://github.com/rampantpixels/rpmalloc[rpmalloc] instead of malloc/free for memory allocation in ParparVM. This will cause the deployment target to be changed to a minimum of iOS 8.0. |ios.statusbar_hidden @@ -298,31 +299,31 @@ Currently only supported for App Store builds. See https://www.codenameone.com/ |Space-delimited list of keychain access groups that this app has access to as described in https://developer.apple.com/library/content/documentation/Security/Conceptual/keychainServConcepts/02concepts/concepts.html#//apple_ref/doc/uid/TP30000897-CH204-SW11[Apple's documentation]. These are added to the entitlements file with the key `keychain-access-groups`. |ios.application_exits -|true/false (defaults to false). Indicates whether the application should exit immediately on home button press. The default is to exit, leaving the application running is only partially tested at the moment. +|true/false (defaults to false). Indicates whether the application should exit on home button press. The default is to exit, leaving the application running is only tested at the moment. |ios.blockScreenshotsOnEnterBackground -|true/false (defaults to false). Indicates that app should prevent iOS from taking screenshots when app enters background. Described https://shannah.github.io/cn1-recipes/#_hiding_sensitive_data_when_entering_background[here]. +|true/false (defaults to false). Indicates that app should prevent iOS from taking screenshots when app enters background. Described https://shannah.github.io/cn1-recipes/#_hiding_sensitive_data_when_entering_background[here]. |ios.detectJailbreak -|true/false (defaults to false). When true, the iOS app will exit on launch if it detects that it's running on a jailbroken device. +|true/false (defaults to false). When true, the iOS app will exit on launch if it detects that it's running on a jailbroken device. |ios.applicationQueriesSchemes -|Comma separated list of url schemes that `canExecute` will respect on iOS. If the url scheme isn't mentioned here `canExecute` will return false starting with iOS 9. Notice that this collides with `ios.plistInject` when used with the `LSApplicationQueriesSchemes...` value so you should use one or the other. E.g. to enable `canExecute` for a url like `myurl://xys` you can use: `myurl,myotherurl` +|Comma separated list of url schemes that `canExecute` will respect on iOS. If the url scheme isn't mentioned here `canExecute` will return false starting with iOS 9. Notice that this collides with `ios.plistInject` when used with the `LSApplicationQueriesSchemes...` value so you should use one or the other. For example, to enable `canExecute` for a url like `myurl://xys` you can use: `myurl,myotherurl` |ios.themeMode |`auto` (default), `modern`, `ios7`, `legacy`. `auto` (unset) keeps the existing iOS 7 flat theme so pre-refactor screenshot goldens and apps see no behavior change. `modern` / `liquid` opts in to the CSS-generated iOS Modern (liquid-glass) theme shipped from `native-themes/ios-modern/theme.css`. `ios7` / `flat` is the same as `auto` - pre-liquid iOS 7 flat theme; `legacy` / `iphone` loads the pre-iOS 7 iPhone theme. The `auto` -> modern flip is planned for a future release. |cn1.androidTheme -|`hololight` (default), `material`, `legacy`. Default is `hololight` (Android Holo Light, what the framework shipped on API 14+ before this refactor). `material` / `modern` opts in to the CSS-generated Android Material 3 theme from `native-themes/android-material/theme.css`. `legacy` loads the pre-Holo Android theme. `and.hololight=true` is still accepted for back-compat and maps to `hololight`. The default stays on `hololight` until we flip in a future release. +|`hololight` (default), `material`, `legacy`. Default is `hololight` (Android Holo Light, what the framework shipped on API 14+ before this refactor). `material` / `modern` opts in to the CSS-generated Android Material 3 theme from `native-themes/android-material/theme.css`. `legacy` loads the pre-Holo Android theme. `and.hololight=true` is still accepted for back-compat and maps to `hololight`. The default stays on `hololight` until you flip in a future release. |cn1.nativeTheme -|`modern`, `legacy`, `custom` (default unset). Cross-platform override that sets both `ios.themeMode` and `cn1.androidTheme` together when those are not set explicitly. `modern` = liquid glass + Material 3, `legacy` = iOS 7 flat + Holo Light, `custom` disables the framework native theme entirely. +|`modern`, `legacy`, `custom` (default unset). Cross-platform override that sets both `ios.themeMode` and `cn1.androidTheme` together when those aren't set explicitly. `modern` = liquid glass + Material 3, `legacy` = iOS 7 flat + Holo Light, `custom` disables the framework native theme entirely. |ios.interface_orientation |UIInterfaceOrientationPortrait by default. Indicates the orientation, one or more of (separated by colon :): `UIInterfaceOrientationPortrait`, `UIInterfaceOrientationPortraitUpsideDown`, `UIInterfaceOrientationLandscapeLeft`, `UIInterfaceOrientationLandscapeRight`. Notice that the IDE plugin has an "Interface Orientation" combo box you *should* use under the iOS section. |ios.xcode_version -|The version of Xcode used on the server. Defaults to 4.5; currently accepts 5.0 as an option and nothing else. +|The version of Xcode used on the server. Defaults to 4.5; accepts 5.0 as an option and nothing else. |ios.multitasking |Set to true to enable iOS multitasking and split-screen support. This only works if `ios.xcode_verson=9.2`. @@ -331,7 +332,7 @@ Currently only supported for App Store builds. See https://www.codenameone.com/ |Valid values include 5 or 8. Indicates the JVM version that should be used for server compilation, this is defined by default for newly created apps based on the Java 8 mode selection |javascript.inject_proxy -|true/false (defaults to `true`) By default, the build server will configure the .war version of your app to use the bundled proxy servlet for HTTP requests (to get around same-origin restrictions on network requests). Setting this to `false` prevents this, causing the application to make network requests without a proxy. +|true/false (defaults to `true`) By default, the build server will configure the .war version of your app to use the bundled proxy servlet for HTTP requests (to get around same-origin restrictions on network requests). Setting this to `false` prevents this, causing the application to make network requests without a proxy. |javascript.inject.beforeHead | Content to be injected into the index.html file at the beginning of the `` tag. @@ -340,16 +341,16 @@ Currently only supported for App Store builds. See https://www.codenameone.com/ | Content to be injected into the index.html file at the end of the `` tag. |javascript.minifying -|true/false (defaults to `true`). By default the javascript code is minified to reduce file size. You may optionally disable minification by setting `javascript.minifying` to `false`. +|true/false (defaults to `true`). By default the JavaScript code is minified to reduce file size. You may optionally disable minification by setting `javascript.minifying` to `false`. |javascript.proxy.url -|The URL to the proxy servlet that should be used for making network requests. If this is omitted, the .war version of the app will be set to use the bundled proxy servlet, and the .zip version of the app will be set to use no proxy. If `javascript.inject_proxy` is `false`, this build-hint will be ignored. +|The URL to the proxy servlet that should be used for making network requests. If this is omitted, the .war version of the app will be set to use the bundled proxy servlet, and the .zip version of the app will be set to use no proxy. If `javascript.inject_proxy` is `false`, this build-hint will be ignored. |javascript.sourceFilesCopied -|true/false (defaults to `false`). Setting this flag to `true` will cause available java source files to be included in the resulting .zip and .war files. These may be used by Chrome during debugging. +|true/false (defaults to `false`). Setting this flag to `true` will cause available java source files to be included in the resulting .zip and .war files. These may be used by Chrome during debugging. |javascript.stopOnErrors -|true/false (defaults to `true`). Cause javascript build to fail if there are warnings during the build. In some cases build warnings won't affect the running of the app. E.g. if the Javascript port is missing a method that the app depends on, but it isn't used in most of the app. Or if there is multithreaded code detected in static initializers, but that code-path isn't used by the app. Setting this to `false` may allow you to get past some build errors, but it might just result in runtime errors later on, which are much more difficult to debug. *This build hint is only available in Codename One 3.4 and later. +|true/false (defaults to `true`). Cause JavaScript build to fail if there are warnings during the build. Sometimes build warnings won't affect the running of the app. For example, if the Javascript port is missing a method that the app depends on, but it isn't used in most of the app. Or if there is multithreaded code detected in static initializers, but that code-path isn't used by the app. Setting this to `false` may allow you to get past some build errors, but it might just result in runtime errors later on, which are much more difficult to debug. *This build hint is only available in Codename One 3.4 and later. |javascript.teavm.version | (Optional) The version of TeaVM to use for the build. *Use caution*, only use this property if you know what you're doing! @@ -359,7 +360,7 @@ Currently only supported for App Store builds. See https://www.codenameone.com/ |Allows integrating Admob/Google Play ads into the application see link:https://www.codenameone.com/blog/adding-google-play-ads.html[this] |ios.entitlementsInject -|Content to inject into the iOS entitlements file. This should be in the Plist XML format. See https://developer.apple.com/documentation/bundleresources/entitlements?language=objc[Apple Entitlements Documentation]. +|Content to inject into the iOS entitlements file. This should be in the Plist XML format. See https://developer.apple.com/documentation/bundleresources/entitlements?language=objc[Apple Entitlements Documentation]. |ios.plistInject |entries to inject into the iOS plist file during build. @@ -395,7 +396,7 @@ Currently only supported for App Store builds. See https://www.codenameone.com/ |Boolean true/false defaults to true. Clears the badge value with every load of the app, this is useful if the app doesn't manually keep track of number values for the badge |ios.glAppDelegateHeader -|Objective-C code that can be injected into the iOS app delegate at the top of the file. E.g. if you need to include headers or make special imports for other injected code +|Objective-C code that can be injected into the iOS app delegate at the top of the file. For example, if you need to include headers or make special imports for other injected code |ios.glAppDelegateBody |Objective-C code that can be injected into the iOS app delegate within the body of the file before the end. This only makes sence for methods that aren't already declared in the class @@ -410,19 +411,21 @@ Currently only supported for App Store builds. See https://www.codenameone.com/ |This flag is required for iOS 8 and newer if you're using the location API. It needs to include a description of the reason for which you need access to the users location |ios.NSXXXUsageDescription -|iOS privacy flags for using certain APIs. Starting with Xcode 8, you're required to add usage description strings for certain APIs. Find a full list of the available keys in https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html[Apple's docs]. Some relevant ones include `ios.NSCameraUsageDescription`, `ios.NSContactsUsageDescription`, `ios.NSLocationAlwaysUsageDescription`, `NSLocationUsageDescription`, `ios.NSMicrophoneUsageDescription`, `ios.NSPhotoLibraryAddUsageDescription`, `ios.NSSpeechRecognitionUsageDescription`, `ios.NSSiriUsageDescription` +|iOS privacy flags for using certain APIs. Starting with Xcode 8, you're required to add usage description strings for certain APIs. Find a full list of the available keys in https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html[Apple's docs]. Some relevant ones include `ios.NSCameraUsageDescription`, `ios.NSContactsUsageDescription`, `ios.NSLocationAlwaysUsageDescription`, `NSLocationUsageDescription`, `ios.NSMicrophoneUsageDescription`, `ios.NSPhotoLibraryAddUsageDescription`, `ios.NSSpeechRecognitionUsageDescription`, `ios.NSSiriUsageDescription` |ios.add_libs -|A semicolon separated list of libraries that should be linked to the app in order to build it +|A semicolon separated list of libraries that should be linked to the app to build it |ios.pods -|A comma separated list of https://cocoapods.org/[Cocoa Pods] that should be linked to the app in order to build it. E.g. `AFNetworking ~> 2.6, ORStackView ~> 3.0, SwiftyJSON ~> 2.3` +|A comma separated list of https://cocoapods.org/[Cocoa Pods] that should be linked to the app to build it. For example, `AFNetworking ~> 2.6, ORStackView ~> 3.0, SwiftyJSON ~> 2.3` |ios.pods.platform -| Sets the Cocoapods 'platform' for the Cocoapods. Some Cocoapods require a minimum platform level. E.g. `ios.pods.platform=7.0`. +// vale-skip: write-good.TooWordy — 'minimum platform level' is the standard CocoaPods term; 'least platform level' is wrong. +| Sets the Cocoapods 'platform' for the Cocoapods. Some Cocoapods require a minimum platform level. For example, `ios.pods.platform=7.0`. | ios.deployment_target -| Sets the deployment target for iOS builds. This is the minimum version of iOS required by a device to install the app. E.g. `ios.deployment_target=8.0`. Default is '6.0'. Note: This build hint interacts with the `ios.rpmalloc` build hint. If `ios.deployment_target` is 8.0 or higher, ParparVM will use https://github.com/rampantpixels/rpmalloc[rpmalloc] by default. You can disable this default and revert back to using malloc/free by setting the `ios.rpmalloc=false` build hint. +// vale-skip: write-good.TooWordy — 'minimum version' is the standard term for a deployment-target floor. +| Sets the deployment target for iOS builds. This is the minimum version of iOS required by a device to install the app. For example, `ios.deployment_target=8.0`. Default is '6.0'. Note: This build hint interacts with the `ios.rpmalloc` build hint. If `ios.deployment_target` is 8.0 or higher, ParparVM will use https://github.com/rampantpixels/rpmalloc[rpmalloc] by default. You can disable this default and revert back to using malloc/free by setting the `ios.rpmalloc=false` build hint. |ios.bundleVersion |Indicates the version number of the bundle, this is useful if you want to create a minor version number change for the beta testing support @@ -434,7 +437,7 @@ Currently only supported for App Store builds. See https://www.codenameone.com/ |Boolean true/false defaults to false and works only for pro accounts. Enables the testflight support in the release binaries for easy beta testing. Notice that the IDE plugin has a "Test Flight" check box you *should* use under the iOS section. |ios.generateSplashScreens -|Boolean true/false defaults to false as of 5.0. Enable legacy generation of splash screen images for use when launching the app. These have been replaced now by the new launch storyboards. +|Boolean true/false defaults to false as of 5.0. Enable legacy generation of splash screen images for use when launching the app. These have been replaced now by the new launch storyboards. |desktop.width |Width in pixels for the form in desktop builds, will be doubled for retina grade displays. Defaults to 800. @@ -464,16 +467,16 @@ Currently only supported for App Store builds. See https://www.codenameone.com/ |Can be exe or msi depending on desired results |desktop.win.cef -|Whether to use CEF for media and BrowserComponent instead of JavaFX in windows desktop builds. true/false. Currently default value is `false` (Jan 2021), but this will be changed to `true` in a future version. +|Whether to use CEF for media and BrowserComponent instead of JavaFX in windows desktop builds. true/false. Default value is `false` (Jan 2021), but this will be changed to `true` in a future version. |desktop.mac.cef -|Whetherto use CEF for media or BrowserComponent instead of JavaFX in Mac desktop builds. true/false. Currently default value is `false` (Jan 2021), but this will be changed to `true` in a future version. +|Whetherto use CEF for media or BrowserComponent instead of JavaFX in Mac desktop builds. true/false. Default value is `false` (Jan 2021), but this will be changed to `true` in a future version. |mac.desktop-vm -|The JVM the should be bundled with Mac desktop build. Mac desktop builds only. Supported values: zuluFx8, zulu11, zuluFx11 +|The JVM the should be bundled with Mac desktop build. Mac desktop builds only. Supported values: zuluFx8, zulu11, zuluFx11 |win.desktop-vm -|The JVM that should be bundled in the Windows desktop build. Windows desktop builds only. Supported values: zulu8, zuluFx8, zulu8-32bit, zuluFx8-32bit, zulu11, zuluFx11, zulu11-32bit, zuluFx11-32bit +|The JVM that should be bundled in the Windows desktop build. Windows desktop builds only. Supported values: zulu8, zuluFx8, zulu8-32bit, zuluFx8-32bit, zulu11, zuluFx11, zulu11-32bit, zuluFx11-32bit |windows.extensions |Historical build hint for the discontinued UWP target. it's retained here only for legacy reference and isn't used by current supported build targets. @@ -482,17 +485,17 @@ Currently only supported for App Store builds. See https://www.codenameone.com/ |true/false (defaults to false). Forces windows desktop builds to use the Win32 JVM instead of the 64 bit VM making them compatible with older Windows Machines. This is off by default at the moment because of a bug in JDK 8 update 112 that might cause this to fail for some cases |win.installDirName -|Windows desktop builds only. Overrides the default installation folder name suggested by the installer (under `Program Files`). Defaults to the application's main class name for backward compatibility. Use this build hint to set a user-friendly installation folder name (e.g. `win.installDirName=My Application`). The application id used by Windows for upgrade detection is unaffected, so existing installs continue to upgrade correctly. +|Windows desktop builds only. Overrides the default installation folder name suggested by the installer (under `Program Files`). Defaults to the application's main class name for backward compatibility. Use this build hint to set a user-friendly installation folder name (for example, `win.installDirName=My Application`). The application id used by Windows for upgrade detection is unaffected, so existing installs continue to upgrade. |win.shortcutName -|Windows desktop builds only. Overrides the name used for the Start Menu shortcut, the Desktop shortcut and (when `win.launchOnStart=true`) the autostart shortcut. Defaults to the application's main class name for backward compatibility. Use this build hint to set a user-friendly shortcut label (e.g. `win.shortcutName=My Application`). +|Windows desktop builds only. Overrides the name used for the Start Menu shortcut, the Desktop shortcut and (when `win.launchOnStart=true`) the autostart shortcut. Defaults to the application's main class name for backward compatibility. Use this build hint to set a user-friendly shortcut label (for example, `win.shortcutName=My Application`). |noExtraResources |true/false (defaults to false). Blocks codename one from injecting its own resources when set to true, the only effect this has is in slightly reducing archive size. This might have adverse effects on some features of Codename One so it isn't recommended. |=== -=== Android Permissions +=== Android permissions One of the annoying tasks when programming native Android applications is tuning all the required permissions to match your codes requirements, Codename One aims to simplify this. The build server automatically introspects the classes sent to it as part of the build and injects the right set of permissions required by the app. @@ -506,11 +509,11 @@ a specific permission came up. This maps Android permissions to the methods/clas `android.permission.CAMERA` & `android.permission.RECORD_AUDIO` - required when capturing photos, video, or audio. The builder injects them automatically and the Android port prompts the user the first time the API is used. -`android.permission.READ_PHONE_STATE` - used by telephony-aware APIs like `Display.getMsisdn()` and by media integration so audio pauses correctly on calls. +`android.permission.READ_PHONE_STATE` - used by telephony-aware APIs like `Display.getMsisdn()` and by media integration so audio pauses on calls. `android.permission.ACCESS_FINE_LOCATION` & `android.permission.ACCESS_COARSE_LOCATION` - requested when using `com.codename1.location` or embedded maps so location fixes can be delivered. -`android.permission.ACCESS_BACKGROUND_LOCATION` - added when background geofencing or fetch tasks are enabled; the Android port verifies the permission separately on Android 10+. +`android.permission.ACCESS_BACKGROUND_LOCATION` - added when background geofencing or fetch tasks are enabled; the Android port verifies the permission on Android 10+. `android.permission.POST_NOTIFICATIONS` - prompted on Android 13+ for both push registration and local notifications. @@ -518,7 +521,7 @@ a specific permission came up. This maps Android permissions to the methods/clas `android.permission.READ_CONTACTS` - requested when accessing the device address book through `Display.getAllContacts()` and related APIs. -==== Permissions Under Marshmallow (Android 6+) +==== Permissions under Marshmallow (Android 6+) Starting with Marshmallow (Android 6+ API level 23) Android shifted to a permissions system that prompts users for permission the first time an API is used for example: when accessing contacts the user will receive a prompt whether to allow contacts access. @@ -526,11 +529,12 @@ NOTE: Permission can be denied and a user can later on revoke/grant a permission This is great as it allows apps to be installed with a single click and no permission prompt during install which can increase conversion rates! -===== Enabling Permissions +===== Enabling permissions +// vale-skip: write-good.TooWordy — 'minimum of API 33' is the precise term for the SDK floor. Codename One's Gradle 8 based Android builder detects the highest Android SDK you've installed and uses that value (with a minimum of API 33) for both the compile and target SDK versions, so the modern runtime permission flow is enabled by default. If you override the target version through the `android.targetSDKVersion` build hint the builder will honour it, but lowering the target may disable some compatibility libraries. Keeping the target current is strongly recommended for Play Store compliance. -===== Permission Prompts +===== Permission prompts To test this API see the following simple contacts app: @@ -569,11 +573,11 @@ image::img/marshmallow-permissions-second-request.png[Native permission prompt s Notice that denying this second request won't trigger another Codename One prompt. -===== Code Changes +===== Code changes -There are no explicit code changes needed for this functionality to "work". The respective API's will work like they always worked and will prompt the user seamlessly for permissions. +no explicit code changes needed for this functionality to "work." The respective API's will work like they always worked and will prompt the user seamlessly for permissions. -TIP: Some behaviors that never occurred on Android but were perfectly legal in the past might start occurring with the switch to the new API. For example: the location manager might be null and your app must always be ready to deal with such a situation +TIP: Some behaviors that never occurred on Android but were legal in the past might start occurring with the switch to the new API. For example: the location manager might be null and your app must always be ready to deal with such a situation When permission is requested a user will be seamlessly prompted/warned. You can customize the permission text through `Display` properties. For example: to customize the rationale text of the contacts permission: @@ -605,7 +609,7 @@ The same pattern applies to other permissions. `android.permission.ACCESS_BACKGR This is optional as there is a default value defined. You can define this once in the `init(Object)` method but for some extreme cases permission might be needed for different things for example: you might ask for this permission with one reason at one point in the app and with a different reason at another point in the app. -===== Simulating Prompts +===== Simulating prompts You can simulate permission prompts by checking that option in the simulator menu. @@ -618,7 +622,7 @@ This will produce a dialog to the user whenever this happens in Android and will If you write Android native code using native interfaces you're probably familiar with the `AndroidNativeUtil` class from the `com.codename1.impl.android` package. -This class provides access to many low-level capabilities you would need as a developer writing native code. Since native code might need to request a permission you introduced the same underlying logic you used namely: +This class provides access to many low-level capabilities you would need as a developer writing native code. Since native code might need to request a permission Codename One uses the same underlying logic, namely: `checkForPermission`. To get a permission you can use this code as such: @@ -628,7 +632,7 @@ To get a permission you can use this code as such: include::../demos/android/src/main/java/com/codenameone/developerguide/advancedtopics/PermissionSnippets.java[tag=androidCheckForPermission,indent=0] ---- -This will prompt the user with the native UI and later on with the fallback option as described above. Notice that the `checkForPermission` method is a blocking method and it will return when there is a final conclusion on the subject. It uses `invokeAndBlock` and can be safely invoked on the event dispatch thread without concern. +This will prompt the user with the native UI and later on with the fallback option as described above. Notice that the `checkForPermission` method is a blocking method and it will return when there is a final conclusion on the subject. It uses `invokeAndBlock` and can be invoked on the event dispatch thread without concern. By default, fallback prompts are displayed using Codename One's `Dialog.show(...)`. If you're writing native Android code and need to use your own prompt implementation, you can install a custom callback: @@ -639,17 +643,17 @@ include::../demos/android/src/main/java/com/codenameone/developerguide/advancedt Pass `null` to `AndroidNativeUtil.setPermissionPromptCallback()` to restore the default behavior. -=== On Device Debugging +=== On device debugging Codename One supports debugging applications on devices by using the natively generated project. All paid subscription levels include the ability to check an #Include Source# flag in the settings that returns a native OS project. You can debug that project in the respective native IDE. -In iOS this is usually strait forward, open the project with Xcode and run it optionally disabling bitcode. Unzip the.bz2 file and open the `.xcworkspace` file if it's available otherwise open the `.xcodeproj` file inside the `dist` directory. +In iOS this is strait forward, open the project with Xcode and run it optionally disabling bitcode. Unzip the.bz2 file and open the `.xcworkspace` file if it's available otherwise open the `.xcodeproj` file inside the `dist` directory. IMPORTANT: The `.xcworkspace` is no longer exclusive to CocoaPods-based builds. Use it whenever it's generated, whether the project uses CocoaPods, Swift Package Manager, or both. -With Android Studio this is sometimes as easy task as it's possible to actually open the Gradle project in Android Studio and run it. For example, due to the fragile nature of the Gradle project this stopped working for some builds and has been "flaky". +With Android Studio this is sometimes as easy task as it's possible to actually open the Gradle project in Android Studio and run it. For example, due to the fragile nature of the Gradle project this stopped working for some builds and has been "flaky." -==== Android Studio Debugging (Easy Way) +==== Android Studio debugging (easy way) By default you should be able to open the Gradle project in Android Studio and run it. To get this to work open the Android Studio #Setting# and select Gradle *2.11*. @@ -658,9 +662,9 @@ image::img/gradle-settings.png[Gradle settings UI in Android Studio,scaledwidth= If this works for you then you can ignore the section below. -==== Android Studio Debugging the Hard Way +==== Android Studio debugging the hard way -In some cases the Gradle project might not work or this might fail with a change from Google. +Sometimes the Gradle project might not work or this might fail with a change from Google. Here are steps that should work for everyone: @@ -673,22 +677,22 @@ Here are steps that should work for everyone: . Copy the source Gradle dependencies content to the destination Gradle file . Connect your device and press the Debug button for the IDE -NOTE: You might need to copy additional Gradle file meta-data such as multi-dexing etc. +NOTE: You might need to copy more Gradle file meta-data such as multi-dexing etc. You might not need to repeat the whole thing with every build. For example: it might be practical to copy the `userSources.jar` from the libs directory to get the latest version of your code. You can copy the `src/main` directory to get the latest up to date Android port. -=== Native Interfaces +=== Native interfaces Sometimes you may wish to use an API that's unsupported by Codename One or integrate with a 3rd party library/framework that isn't supported. These are achievable tasks when writing native code and Codename One lets you encapsulate such native code using native interfaces. ==== Introduction -Notice that when you say "native" you don't mean C/C++ always but rather the platforms "native" environment. So in the case of Android, Java or Kotlin code can be invoked with full access to the Android API. In case of iOS, Objective-C or Swift code can be invoked and so forth. +Notice that when you say "native" you don't mean C/C++ always but rather the platforms "native" environment. for Android, Java or Kotlin code can be invoked with full access to the Android API. In case of iOS, Objective-C or Swift code can be invoked and so forth. TIP: You can still access C code under Android either by using JNI from the Android native code or by using a library -Native interfaces are designed to allow primitive types, Strings, arrays of primitive types (single dimension ) & https://www.codenameone.com/javadoc/com/codename1/ui/PeerComponent.html[PeerComponent] values. Any other type of parameter/return type is prohibited. For example, once in the native layer the native code can act freely and query the Java layer for additional information. +Native interfaces are designed to allow primitive types, Strings, arrays of primitive types (single dimension ) & https://www.codenameone.com/javadoc/com/codename1/ui/PeerComponent.html[PeerComponent] values. Any other kind of parameter/return type is prohibited. For example, once in the native layer the native code can act and query the Java layer for more information. NOTE: The reason for the limits is the disparity between the platforms. Mapping a Java `Object` to an Objective-C `NSObject` is possible but leads to odd edge cases and complexity for example: GC vs. ARC in a disparate object graph @@ -726,8 +730,8 @@ These are effectively stubs you can edit to implement the methods in native code TIP: If you re-run the #Generate Native Access# tool you will get this dialog, if you answer yes all the files will be overwritten, if you answer no files you deleted/renamed will be recreated -.Running "Generate Native Access" when some/all of the native files exist already -image::img/native-interfaces-generated-existing.png[Running "Generate Native Access" when some/all of the native files exist already,scaledwidth=40%] +.Running "Generate Native Access" when some/all the native files exist already +image::img/native-interfaces-generated-existing.png[Running "Generate Native Access" when some/all the native files exist already,scaledwidth=40%] For now lets leave the stubs and come back to them soon. From the Codename One Java code you can call the implementation of this native interface using: @@ -759,7 +763,7 @@ include::../demos/android/src/main/java/com/mycompany/myapp/MyNativeImpl.java[ta <2> The impl class doesn't physically implement the `MyNative` interface! + This is intentional and due to the `PeerComponent` functionality mentioned below. You don't need to add an implements clause. -<3> Notice that there is no constructor and the class is public. it's crucial that the system will be able to allocate the class without obstruction. You can use a constructor but it can't have any arguments and you shouldn't rely on semantics of construction. +<3> Notice that there is no constructor and the class is public. it's crucial that the system will be able to assign the class without obstruction. You can use a constructor but it can't have any arguments and you shouldn't rely on semantics of construction. <4> You implemented the native method and that you set `isSupported` to true. @@ -768,7 +772,7 @@ Codename One doesn't include the native platforms in its bundle for example: the TIP: When implementing a non-trivial native interface, send a server build with the "Include Source" option checked. Implement the native interface in the native IDE then copy and paste the native code back into Codename One -The implementation of this interface is nearly identical for Android (Java/Kotlin) & Java SE. +The implementation of this interface is identical for Android (Java/Kotlin) & Java SE. ===== Swift (iOS) and Kotlin (Android) options @@ -790,18 +794,18 @@ For Android native interfaces you can implement the generated `...Impl` class in `ios/src/main/objectivec/com_mycompany_myapp_MyNativeImpl.swift` |=== -===== Use the Android Main Thread (Native EDT) +===== Use the Android main thread (native EDT) -iOS, Android & pretty much any modern OS has an EDT like thread that handles events etc. The problem is that they differ in their nuanced behavior. For example: Android will usually respect calls off of the EDT and iOS will often crash. Some OS's enforce EDT access rigidly and will throw an exception when you violate that... +iOS, Android & pretty much any modern OS has an EDT like thread that handles events etc. The problem is that they differ in their nuanced behavior. For example: Android will respect calls off of the EDT and iOS will often crash. Some OS's enforce EDT access rigidly and will throw an exception when you violate that... -Normally you don't need to know about these things, hidden functionality within your implementation bridges between your EDT and the native EDT to provide consistent cross platform behavior. But when you write native code you need awareness. +You don't need to know about these things, hidden functionality within your implementation bridges between your EDT and the native EDT to provide consistent cross platform behavior. However, when you write native code you need awareness. .Why not Implicitly call Native Interfaces on the Native EDT? **** -Calling into the native EDT includes overhead and it might not be necessary for some features (e.g. IO, polling etc.). Furthermore, some calls might work well with asynchronous calls while others might need synchronous results and you can't know in advance which ones you would need. +Calling into the native EDT includes overhead and it might not be necessary for some features (for example, IO, polling etc.). Furthermore, some calls might work well with asynchronous calls while others might need synchronous results and you can't know in advance which ones you would need. **** -====== How do you Access the Native EDT? +====== How do you access the native EDT Within your native code in Android do something like: @@ -820,7 +824,7 @@ include::../demos/android/src/main/java/com/codenameone/developerguide/advancedt This blocks in a way that's OK with the Codename One EDT which is unique to your Android port. -===== Gradle Dependencies +===== Gradle dependencies Integrating a native OS library isn't hard but it sometimes requires some juggling. Most instructions target developers working with Xcode or Android Studio & you need to twist your head around them. In Android the steps for integration in most modern libraries include a Gradle dependency. @@ -874,10 +878,10 @@ And `com_mycompany_myapp_MyNativeImpl.m` contains: include::../demos/ios/src/main/objectivec/com_mycompany_myapp_MyNativeImpl.m[tag=myNativeImplStub,indent=0] ---- -IMPORTANT: Objective-C relies on argument names as part of the message (method) signature. So `-(NSString*)helloWorld:(NSString*)param` isn't the same as `-(NSString*)helloWorld:(NSString*)iChangedThisName`! + +IMPORTANT: Objective-C relies on argument names as part of the message (method) signature. `-(NSString*)helloWorld:(NSString*)param` isn't the same as `-(NSString*)helloWorld:(NSString*)iChangedThisName`! + don't change argument names in the Objective-C native interface! -Here is a simple implementation similar to above: +Here is a simple implementation like above: [source,objective-c] ---- @@ -886,11 +890,11 @@ include::../demos/ios/src/main/objectivec/com_mycompany_myapp_MyNativeImpl.m[tag If you prefer Swift for iOS native interfaces, keep the same class naming convention (`com_mycompany_myapp_MyNativeImpl`) and annotate the class with `@objc(...)` so the runtime can discover it. -===== Using the iOS Main Thread (Native EDT) +===== Using the iOS main thread (native EDT) iOS has a native thread you should use for all calls like Android. Check out the Native EDT on Android section above for reference. -On iOS this is pretty similar to Android (if you consider objective-c to be similar). This is used for asynchronous invocation: +On iOS this is pretty like Android (if you consider objective-c to be similar). This is used for asynchronous invocation: [source,objc] ---- @@ -906,13 +910,13 @@ include::../demos/ios/src/main/objectivec/DispatchExamples.m[tag=dispatchSync,in The problem with the synchronous call is that it will block the caller thread, if the caller thread is the EDT this can cause performance issues and even a deadlock. it's important to be cautious with this call! -===== Use Cocoapods For Dependencies +===== Use Cocoapods for dependencies Cocoapods are the iOS equivalent of Gradle dependencies. -CocoaPods allow you to add a native library dependency to iOS far more easily than Gradle. By default you target iOS 7.0 or newer which is supported by Intercom for older versions of the library. Annoyingly CocoaPods might seem to work but some specific API's won't work since it fell back to an older version... To solve this you've to explicitly define the build hint `ios.pods.platform=8.0` to force iOS 8 or newer. You might need to force it to even newer versions as some libraries force an iOS 9 minimum etc. +CocoaPods allow you to add a native library dependency to iOS far more than Gradle. By default you target iOS 7.0 or newer which is supported by Intercom for older versions of the library. Annoyingly CocoaPods might seem to work but some specific API's won't work since it fell back to an older version... To solve this you've to explicitly define the build hint `ios.pods.platform=8.0` to force iOS 8 or newer. You might need to force it to even newer versions as some libraries force an iOS 9 least etc. -Including intercom itself required a single build hint: `ios.pods=Intercom` which you can obviously extend by using commas to include many libraries. You can search the https://cocoapods.org/[cocoapods website] for supported 3rd party libraries which includes everything you would expect. One important advantage when working with CocoaPods is the faster build time as the upload to the Codename One website is smaller and the bandwidth you've to CocoaPods is faster. Another advantage is the ability to keep up with the latest developments from the library providers. +Including intercom itself required a single build hint: `ios.pods=Intercom` which you can obviously extend by using commas to include many libraries. You can search the https://cocoapods.org/[CocoaPods website] for supported 3rd party libraries which includes everything you would expect. One important advantage when working with CocoaPods is the faster build time as the upload to the Codename One website is smaller and the bandwidth you've to CocoaPods is faster. Another advantage is the ability to keep up with the latest developments from the library providers. ==== Javascript @@ -921,7 +925,7 @@ Native interfaces in Javascript look a little different than the other platforms The default generated stubs for the JavaScript build look like this `com_mycompany_myapp_MyNative`: -[source,javascript] +[source,JavaScript] ---- (function(exports){ @@ -942,7 +946,7 @@ exports.com_mycompany_myapp_MyNative= o; A simple implementation looks like this. -[source,javascript] +[source,JavaScript] ---- (function(exports){ @@ -972,11 +976,11 @@ The naming conventions for the methods themselves are modeled after the naming c Where `` is the name of the method in Java, and the ``s are a string representing the parameter type. The general rule for these strings are: -1. Primitive types are mapped to their type name. (For example: `int` to "int", `double` to "double", etc...). -2. Reference types are mapped to their fully-qualified class name with '.' replaced with underscores. For example: `java.lang.String` would be "java_lang_String". -3. Array parameters are marked by their scalar type name followed by an underscore and "1ARRAY". For example: `int[]` would be "int_1ARRAY" and `String[]` would be "java_lang_String_1ARRAY". +1. Primitive types are mapped to their type name. (For example: `int` to "int," `double` to "double," etc...). +2. Reference types are mapped to their fully qualified class name with '.' replaced with underscores. For example: `java.lang.String` would be `java_lang_String`. +3. Array parameters are marked by their scalar type name followed by an underscore and `1ARRAY`. For example: `int[]` would be "int_1ARRAY" and `String[]` would be `java_lang_String_1ARRAY`. -===== JavaScript Examples +===== JavaScript examples Java API: @@ -987,7 +991,7 @@ include::../demos/common/src/main/java/com/codenameone/developerguide/advancedto becomes -[source,javascript] +[source,JavaScript] ---- o.print__java_lang_String = function(param1, callback) { console.log(param1); @@ -1004,7 +1008,7 @@ include::../demos/common/src/main/java/com/codenameone/developerguide/advancedto becomes -[source,javascript] +[source,JavaScript] ---- o.add__int_int = function(param1, param2, callback) { callback.complete(param1 + param2); @@ -1018,7 +1022,7 @@ include::../demos/common/src/main/java/com/codenameone/developerguide/advancedto becomes -[source,javascript] +[source,JavaScript] ---- o.add__int_1ARRAY = function(param1, callback) { var c = 0, len = param1.length; @@ -1029,7 +1033,7 @@ o.add__int_1ARRAY = function(param1, callback) { } ---- -==== Native GUI Components +==== Native GUI components https://www.codenameone.com/javadoc/com/codename1/ui/PeerComponent.html[PeerComponent] return values are automatically translated to the platform native peer as an expected return value. For example: for a `NativeInterface` method such as this: @@ -1057,7 +1061,7 @@ Note that this won't limit the code from running on an unsupported platform. Onl Javascript would expect a DOM Element (for example: a `
` tag to be returned.). For example: -[source,javascript] +[source,JavaScript] ---- o.createHelloComponent_ = function(callback) { var c = jQuery('
Hello World
') @@ -1072,9 +1076,9 @@ Notice that if you want to use a native library (jar,.a file etc.) places it wit This is discussed further below. -==== Type Mapping & Rules +==== Type mapping & rules -Several rules govern the creation of NativeInterfaces and you briefly covered some of them. +Several rules govern the creation of NativeInterfaces and you covered some of them. - The implementation class must have a default public constructor or no constructor at all - Native methods can't throw exceptions, checked or otherwise @@ -1092,7 +1096,7 @@ Several rules govern the creation of NativeInterfaces and you briefly covered so |char | char | char | int | char |short | short | short | short | short |int | int | int | int | int -|long | long | long | long long | long +|long | long | long | long | long |float | float | float | float | float |double | double | double | double | double |String | String | String | NSString* | String @@ -1111,7 +1115,7 @@ TIP: JavaScript is excluded from the table above as it isn't a type safe languag NOTE: `PeerComponent` on iOS is `void*` but `UIView` is expected as a result -The examples below demonstrate the signatures for this method on all platforms: +The examples below show the signatures for this method on all platforms: .NativeInterface definition [source,java] @@ -1131,10 +1135,10 @@ include::../demos/android/src/main/java/com/codenameone/developerguide/advancedt include::../demos/ios/src/main/objectivec/PeerComponentExamples.m[tag=iosAllTypesSignature,indent=0] ---- -NOTE: You had to break lines for the print version, the JavaScript version is a long method name that literally broke the book! +NOTE: You had to break lines for the print version, the JavaScript version is a long method name that broke the book! .JavaScript Version -[source,javascript] +[source,JavaScript] ---- o.test__byte_boolean_char_short_int_long_float_double _java_lang_String_byte_1ARRAY_boolean_1ARRAY_char_1ARRAY @@ -1158,13 +1162,13 @@ public void test(byte param, bool param1, char param2, short param3, int param4, } ---- -==== Android Native Permissions +==== Android native permissions -Normally permissions in Codename One are seamless. Codename One traverses the bytecode and automatically assigns permissions to Android applications based on the API’s used by the developer. +Permissions in Codename One are seamless. Codename One traverses the bytecode and automatically assigns permissions to Android applications based on the API’s used by the developer. For example, when accessing native functionality this won’t work since native code might require specialized permissions and you don’t/can’t run any serious analysis on it (it can be about anything). -So if you require additional permissions in your Android native code you need to define them in the build arguments using +if you require more permissions in your Android native code you need to define them in the build arguments using `android.permission.=true` for each permission you want to include. A full list of permissions are listed in Android's https://developer.android.com/reference/android/Manifest.permission.html[Manifest.permission documentation]. For example: @@ -1175,6 +1179,7 @@ android.permission.BATTERY_STATS=true ... ---- +// vale-skip: write-good.TooWordy — 'maximum SDK version' matches the `maxSdkVersion` build hint; 'most' would be wrong English. You can specify the maximum SDK version in which the permission is needed using the `android.permission..maxSdkVersion` build hint. You can also specify whether the permission is *required* for the app to run using the `android.permission..required` build hint. For example: @@ -1187,7 +1192,7 @@ android.permission.ADD_VOICEMAIL.maxSdkVersion=18 ... ---- -You can alternatively use the `android.xpermissions` build hint to inject `` tags into the manifest file. For example: +You can or use the `android.xpermissions` build hint to inject `` tags into the manifest file. For example: ---- android.xpermissions= @@ -1199,7 +1204,7 @@ NOTE: You need to include the full XML snippet. You can unify many lines into a ==== Native AndroidNativeUtil -If you do any native interfaces programming in Android you should be familiar with the `AndroidNativeUtil` class which allows you to access native device functionality more easily from the native code. For example: many Android API's need access to the `Activity` which you can get by calling `AndroidNativeUtil.getActivity()`. +If you do any native interfaces programming in Android you should be familiar with the `AndroidNativeUtil` class which allows you to access native device functionality more from the native code. For example: many Android API's need access to the `Activity` which you can get by calling `AndroidNativeUtil.getActivity()`. The native util class includes a few other features such as: @@ -1209,10 +1214,10 @@ The native util class includes a few other features such as: * `addLifecycleListener`/`removeLifecycleListener` - These essentially provide you with a callback to lifecycle events: `onCreate` etc. which can be pretty useful for some cases. -* `registerViewRenderer` - https://www.codenameone.com/javadoc/com/codename1/ui/PeerComponent.html[PeerComponent]'s are usually shown on top of the UI since they are rendered within - their own thread outside of the EDT cycle. So when you need to show a https://www.codenameone.com/javadoc/com/codename1/ui/Dialog.html[Dialog] on top of the peer you grab a +* `registerViewRenderer` - https://www.codenameone.com/javadoc/com/codename1/ui/PeerComponent.html[PeerComponent]'s are shown on top of the UI since they are rendered within + their own thread outside of the EDT cycle. when you need to show a https://www.codenameone.com/javadoc/com/codename1/ui/Dialog.html[Dialog] on top of the peer you grab a screenshot of the peer, hide it and then show the dialog with the image as the background (the same applies for - transitions). Unfortunately some components (specifically the MapView) might not render properly and require + transitions). Some components (specifically the MapView) might not render and require custom code to implement the transferal to a native Bitmap, this API allows you to do that. You can work with `AndroidNativeUtil` using native code such as this: @@ -1222,22 +1227,22 @@ You can work with `AndroidNativeUtil` using native code such as this: include::../demos/android/src/main/java/com/codenameone/developerguide/advancedtopics/NativeCallsImpl.java[tag=nativeCallsImpl,indent=0] ---- -==== Broadcast Receiver +==== Broadcast receiver A common way to implement features in Android is the `BroadcastReceiver` API. This allows intercepting operating system events for common use cases. A good example is intercepting incoming SMS which is specific to Android so you'd need a broardcast receiver to implement that. This is often confusing to developers who sometimes derive the impl class from broadcast receiver. that's a mistake... -The solution is to place any native Android class into the `native/android` directory. It will get compiled with the rest of the native code and " works". So you can place this class under `native/android/com/codename1/sms/intercept`: +The solution is to place any native Android class into the `native/android` directory. It will get compiled with the rest of the native code and " works." So you can place this class under `native/android/com/codename1/sms/intercept`: [source,java] ---- include::../demos/android/src/main/java/com/codename1/sms/intercept/SMSListener.java[tag=smsListener,indent=0] ---- -The code above is pretty standard native Android code, it's a callback in which most of the logic is similar to the native Android code mentioned in this https://stackoverflow.com/questions/39526138/broadcast-receiver-for-receive-sms-is-not-working-when-declared-in-manifeststat[stackoverflow question]. +The code above is pretty standard native Android code, it's a callback in which most of the logic is like the native Android code mentioned in this https://stackoverflow.com/questions/39526138/broadcast-receiver-for-receive-sms-is-not-working-when-declared-in-manifeststat[stackoverflow question]. -But there is still more you need to do. In order to implement this natively you need to register the permission and the receiver in the `manifest.xml` file as explained in that question. This is how their native manifest looked: +However, there is still more you need to do. To implement this natively you need to register the permission and the receiver in the `manifest.xml` file as explained in that question. This is how their native manifest looked: [source,xml] ---- @@ -1303,7 +1308,7 @@ Here it's formatted nicely: ---- -===== Listening & Permissions +===== Listening & permissions You will notice that these don't include the actual binding or permission prompts you would expect for something like this. To do this you need a native interface. @@ -1330,14 +1335,14 @@ include::../demos/android/src/main/java/com/codename1/sms/intercept/NativeSMSInt <2> Here you actually bind the listener, this allows you to grab one SMS and not listen in on every SMS coming thru -==== Native Code Callbacks +==== Native code callbacks -Native interfaces standardize the invocation of native code from Codename One, but it doesn't standardize the reverse of callbacks into Codename One Java code. The reverse is naturally more complicated since its platform specific and more error prone. +Native interfaces standardize the invocation of native code from Codename One, but it doesn't standardize the reverse of callbacks into Codename One Java code. The reverse is more complicated since its platform specific and more error prone. A common "trick" for calling back is to define a static method and then trigger it from native code. This works -nicely for Android & Java SE since those platforms use Java for their "native code". Mapping this to iOS requires some basic understanding of how the iOS VM works. +nicely for Android & Java SE since those platforms use Java for their "native code." Mapping this to iOS requires some basic understanding of how the iOS VM works. -For the purpose of this explanation lets pretend you've a class called NativeCallback in the src hierarchy under +For this explanation lets pretend you've a class called NativeCallback in the src hierarchy under the package `com.mycompany` that has the method: `public static void callback()`. [source,java] @@ -1346,7 +1351,7 @@ include::../demos/common/src/main/java/com/mycompany/NativeCallback.java[tag=nat ---- -So if I want to call it from Android or all of the Java based platforms I can write this in the "native" code: +if I want to call it from Android or all the Java based platforms I can write this in the "native" code: [source,java] ---- @@ -1360,7 +1365,7 @@ I can also pass a argument as you do later on: include::../demos/common/src/main/java/com/mycompany/NativeCallbackUsage.java[tag=nativeCallbackUsage,indent=0] ---- -===== Accessing Callbacks from Objective-C +===== Accessing callbacks from Objective-C If you want to invoke that method from Objective-C you need to do the following. @@ -1382,9 +1387,9 @@ include::../demos/ios/src/main/objectivec/NativeCallbackSnippets.m[tag=nativeCal TIP: For most callbacks you should use the macro `CN1_THREAD_GET_STATE_PASS_SINGLE_ARG` instead of `CN1_THREAD_STATE_PASS_SINGLE_ARG` also make sure to add `#include "cn1_globals.h" in the file -The VM passes the thread context along method calls to save on API calls (thread context is heavily used in Java for synchronization, gc and more). +The VM passes the thread context along method calls to save on API calls (thread context is used in Java for synchronization, gc and more). -You can easily pass arguments like: +You can pass arguments like: [source,java] ---- @@ -1418,14 +1423,14 @@ include::../demos/common/src/main/java/com/mycompany/NativeCallback.java[tag=nat You would need to convert the `NSString*` value you already have to a `java.lang.String` which the callback expects. -The `fromNSString` function also needs this special argument so you will need to modify the method as such: +The `fromNSString` function also needs this special argument so you will need to change the method as such: [source,objc] ---- include::../demos/ios/src/main/objectivec/NativeCallbackSnippets.m[tag=nativeCallbackInvokeString,indent=0] ---- -And finally you might want to return a value from callback as such: +And you might want to return a value from callback as such: [source,java] ---- @@ -1443,9 +1448,9 @@ The upper case R allows you to differentiate between void `callback(int,int)` an TIP: Covariant return types are a little known Java 5 feature. For example: the method `Object getX()` can be overriden by `MyObject getX()`. For example, in the VM level they can both exist side by side. -===== Accessing Callbacks from Javascript +===== Accessing callbacks from JavaScript -The mechanism for invoking static callback methods from Javascript (for the Javascript port ) is similar to Objective-C's. The `this` object in your native interface method contains a property named `$GLOBAL$` that provides access to static java methods. This object will contain Javascript mirror objects for each Java class (though the property name is mangled by replacing "." with underscores). Each mirror object contains a wrapper method for its underlying class's static methods where the method name follows the same naming convention as is used for the Javascript native methods themselves (and similar to the naming conventions used in Objective-C). +The mechanism for invoking static callback methods from Javascript (for the Javascript port ) is like Objective-C's. The `this` object in your native interface method contains a property named `$GLOBAL$` that provides access to static Java methods. This object will contain Javascript mirror objects for each Java class (though the property name is mangled by replacing "." with underscores). Each mirror object contains a wrapper method for its underlying class's static methods where the method name follows the same naming convention as is used for the Javascript native methods themselves (and like the naming conventions used in Objective-C). For example, the Google Maps project includes the static callback method: @@ -1458,7 +1463,7 @@ defined in the `com.codename1.googlemaps.MapContainer` class. This method is called from Javascript inside a native interface using the following code: -[source,javascript] +[source,JavaScript] ---- var fireMapChangeEvent = this.$GLOBAL$.com_codename1_googlemaps_MapContainer.fireMapChangeEvent__int_int_double_double; google.maps.event.addListener(this.map, 'bounds_changed', function() { @@ -1466,11 +1471,11 @@ google.maps.event.addListener(this.map, 'bounds_changed', function() { }); ---- -In this example you first obtain a reference to the `fireMapChangeEvent` method, and then call it later. For example, you could have called it directly also. +In this example you first get a reference to the `fireMapChangeEvent` method, and then call it later. For example, you could have called it directly also. WARNING: Your code *MUST* contain the full string path `this.$GLOBAL$.your_class_name.your_method_name` or the build server won't be able to recognize that your code requires this method. The `$GLOBAL$` object is populated by the build server with those classes and methods that are used inside your native methods. If the build server doesn't recognize that the methods are being used (through this pattern) it won't generate the necessary wrappers for your Javascript code to access the Java methods. -===== Callbacks of the SMS Receiver +===== Callbacks of the SMS receiver The SMS Broadcast Receiver code from before also used callbacks such as this: @@ -1485,25 +1490,25 @@ include::../demos/common/src/main/java/com/codename1/sms/intercept/SMSCallback.j <2> You wrap the callback in call serially to match the Codename One convention of using the EDT by default. The call will probably arrive on the Android native thread so it makes sense to normalize it and not expose the Android native thread to the user code -===== Asynchronous Callbacks & Threading +===== Asynchronous callbacks & threading -One of the problematic aspects of calling back into Java from Javascript is that Javascript has no notion of multi-threading. Therefore, if the method you're calling uses Java's threads at all (for example: It includes a `wait()`, `notify()`, `sleep()`, `callSerially()`, etc...) you need to call it asynchronously from Javascript. You can call a method asynchronously by appending `$async` to the method name. For example: With the Google Maps example above, you would change : +One of the problematic aspects of calling back into Java from Javascript is that Javascript has no notion of multi-threading. So, if the method you're calling uses Java's threads at all (for example: It includes a `wait()`, `notify()`, `sleep()`, `callSerially()`, etc...) you need to call it asynchronously from Javascript. You can call a method asynchronously by appending `$async` to the method name. For example: With the Google Maps example above, you would change : -[source,javascript] +[source,JavaScript] ---- this.$GLOBAL$.com_codename1_googlemaps_MapContainer.fireMapChangeEvent__int_int_double_double; ---- to -[source,javascript] +[source,JavaScript] ---- this.$GLOBAL$.com_codename1_googlemaps_MapContainer.fireMapChangeEvent__int_int_double_double$async; ---- -This will cause the call to be wrapped in the appropriate bootstrap code to work properly with threads - and it's absolutely necessary in cases where the method *may* use threads of any kind. The side-effect of calling a method with the `$async` suffix is that you can't use return values from the method. +This will cause the call to be wrapped in the appropriate bootstrap code to work with threads - and it's necessary in cases where the method *may* use threads of any kind. The side-effect of calling a method with the `$async` suffix is that you can't use return values from the method. -TIP: In most cases you should use the *async* version of a method when calling it from your native method. Only use the synchronous (default) version if you're absolutely sure that the method doesn't use any threading primitives. +TIP: Usually you should use the *async* version of a method when calling it from your native method. Only use the synchronous (default) version if you're sure that the method doesn't use any threading primitives. === Libraries - cn1lib @@ -1517,7 +1522,7 @@ image::img/cn1libs-dont-change-classpath.png[don't change the classpath, this is Cn1libs are Codename One's file format for 3rd party extensions. it's physicially a zip file containing other zip files and some meta-data. -==== Why Not Use JAR? +==== Why not use JAR A jar can be compiled with usage of any Java API that might not be supported, it can be compiled with a Java target version that isn't tested. @@ -1525,15 +1530,15 @@ Jars don't include support for writing native code, you could use JNI in jars (a Jars don't support "proper" code completion, a common developer trick is to stick source code into the jar but that prevents usage with proprietary code. Cn1libs provide full IDE code completion (with JavaDoc hints) without exposing the sources. -There are two use cases for wanting JAR's and they both have different solutions: +two use cases for wanting JAR's and they both have different solutions: . Modularity . Working with an existing JARs -Cn1lib’s address the modularity aspect allowing you to break that down. Existing jars can sometimes be used native code settings but for the most part you would want to adapt the code to abide by Codename One restrictions. +Cn1lib’s address the modularity aspect allowing you to break that down. Existing jars can sometimes be used native code settings but you would want to adapt the code to abide by Codename One restrictions. -==== How To Use cn1libs? +==== How to use cn1libs Codename One has a large repository of https://www.codenameone.com/cn1libs.html[3rd party cn1libs], you can install a cn1lib by placing it in the lib directory of your project then right clicking the project and selecting #Codename One# -> #Refresh cn1lib files#. @@ -1542,7 +1547,7 @@ image::img/cn1libs-refresh.png[Refresh cn1lib files menu option,scaledwidth=40%] Once refreshed the content of the cn1lib will be available to code completion and you could use it. -TIP: Notice that some cn1libs require additional configurations such as build hints etc. so make sure to read the developers instructions when integrating a 3rd party library. +TIP: Notice that some cn1libs require more configurations such as build hints etc. make sure to read the developers instructions when integrating a 3rd party library. .Under the Hood of cn1lib Install **** @@ -1553,7 +1558,7 @@ Technically that task invokes a custom task that unzips the content of the cn1li The native files are extracted to `lib/impl/native`. The classpath for the main project and the ant build process know about these directories and include them within their path. **** -==== Creating a Simple cn1lib +==== Creating a simple cn1lib Creating a cn1lib is trivial, you will get into more elaborate uses soon enough but for a hello world cn1lib you can use this 2 step process: @@ -1565,7 +1570,7 @@ image::img/cn1lib-create-step2.png[Select the file name/destination. Notice that Once you go thru these steps you can define any source file within the library and it will be accessible to the users of the library. -==== Build Hints in cn1libs +==== Build hints in cn1libs Some cn1libs are pretty simple to install, place them under the lib directory and refresh. For example, many of the more elaborate cn1libs need some pretty complex configurations. This is the case when native code is involved where you need to add permissions or plist entries for the various native platforms to get everything to work. This makes the cn1lib's helpful but less than seamless which is where you want to go. @@ -1578,7 +1583,7 @@ The best way to discover the right syntax for such build hints is to set them th The obvious question is why do you need two files? -There are two types of build hints: required and appended. +two types of build hints: required and appended. Required build hints can be something like `ios.objC=true` which you want to always work. For example: if a cn1lib defines `ios.objC=true` and another cn1lib defines `ios.objC=false` @@ -1598,7 +1603,7 @@ for the developer to investigate issues in this process. - Changing flags is problematic - there is no "uninstall" process. Since the data is copied into the `codenameone_settings.properties` file. If you need to change a flag later on you might need to alert users to make changes to their properties essentially negating the value of this feature... + -So be careful when adding properties here. +be careful when adding properties here. it's your responsibility as a library developer to decide which build hint goes into which file! + Codename One can't automate this process as the whole process of build hints is by definition an ad hoc process. @@ -1729,7 +1734,7 @@ The table below covers the files that can/should be a part of a cn1lib file: **** -=== Integrating Android 3rd Party Libraries & JNI +=== Integrating Android 3rd party libraries & JNI While its pretty easy to use native interfaces to write Android native code some things aren't necessarily as obvious. For example: if you want to integrate a 3rd party library, specifically one that includes native C JNI code this @@ -1756,7 +1761,7 @@ To link a Library project to your Codename One project open the Library project Rename the extension from `.zip` to `.andlib` and place the andlib file under the `native/android` directory. The build server will pick it up and will link it to the project. -=== Drag & Drop +=== Drag & drop Unlike other platforms that tried to create overly generic catch all API's Codename One tried to make things as simple as possible. @@ -1787,8 +1792,8 @@ hi.add(BorderLayout.NORTH, "Arrange The Titles").add(BorderLayout.CENTER, box); hi.show(); ---- -.Drag and drop demo -image::img/draganddrop-rearrange-game.png[Drag and drop demo,scaledwidth=20%] +.Drag demo +image::img/draganddrop-rearrange-game.png[Drag demo,scaledwidth=20%] To enable dragging a component it must be flagged as draggable using `setDraggable(true)`, to allow dropping the component onto another component you must first enable the drop target with `setDropTarget(true)` and override some methods (more on that later). @@ -1800,7 +1805,7 @@ You can override these methods in the draggable components: * `getDragImage` - this generates an image preview of the component that will be dragged. This automatically generates a sensible default so you don't need to override it. -* `drawDraggedImage` - this method will be invoked to draw the dragged image at a given location, it might be useful to override it if you want to display some drag related information such an additional icon based on location etc. (for example: a move/copy icon). +* `drawDraggedImage` - this method will be invoked to draw the dragged image at a given location, it might be useful to override it if you want to display some drag related information such as an icon based on location etc. (for example: a move/copy icon). In the drop target you can override the following methods: @@ -1812,7 +1817,7 @@ In the drop target you can override the following methods: -=== Android Lollipop ActionBar Customization +=== Android Lollipop ActionBar customization When running on Android Lollipop (5.0 or newer) the native action bar will use the Lollipop design. This isn't applicable if you use the https://www.codenameone.com/javadoc/com/codename1/ui/Toolbar.html[Toolbar] or https://www.codenameone.com/javadoc/com/codename1/ui/SideMenuBar.html[SideMenuBar] this will be used in the task switcher. @@ -1829,13 +1834,13 @@ of your project. It should look like this: ---- -=== Intercepting URL's On iOS & Android +=== Intercepting URL's on iOS & Android A common trick in mobile application development, is communication between two unrelated applications. -In Android you can use intents which are pretty elaborate and can be used through `Display.execute`, however what if you would like to expose the functionality of your application to a different application running on the device. This would allow that application to launch your application. +In Android you can use intents which are pretty elaborate and can be used through `Display.execute`, but what if you would like to expose the functionality of your application to a different application running on the device. This would allow that application to launch your application. -This isn't something you built-in to Codename One, however it does expose enough of the platform capabilities to enable that functionality rather easily on Android. +This isn't something built into Codename One, but it does expose enough of the platform capabilities to enable that functionality rather on Android. On Android you need to define an intent filter which you can do using the `android.xintent_filter` build hint, this accepts the XML to filter whether a request is relevant to your application: @@ -1848,7 +1853,7 @@ You can read more about it in link:http://stackoverflow.com/questions/11421048/a To bind the `myapp://` URL to your application. As a result typing `myapp://x` into the Android browser will launch the application. -==== Passing Launch Arguments To The App +==== Passing launch arguments to the app You can access the value of the URL that launched the app using: @@ -1859,13 +1864,13 @@ include::../demos/common/src/main/java/com/codenameone/developerguide/advancedto This value would be null if the app was launched through the icon. -==== Android App-to-App Contracts (Wallet/Issuer style flows) +==== Android App-to-App contracts (Wallet/Issuer style flows) -Some integrations (wallet provisioning, issuer verification, secure step-up) need more than a URL launch. They usually require: +Some integrations (wallet provisioning, issuer verification, secure step-up) need more than a URL launch. They require: - a custom Android intent action, - caller validation, -- asynchronous app logic (login/backend verification), +- asynchronous app logic (login/back end verification), - a native `setResult(...)` response back to the caller. In Codename One, keep this flow in two layers: @@ -1875,7 +1880,7 @@ In Codename One, keep this flow in two layers: ===== Why this is not implicitly generated by AndroidGradleBuilder -A generic bridge can't safely infer: +A generic bridge can't infer: - expected caller package(s), - required result extra names, @@ -1906,7 +1911,7 @@ A minimal bridge activity should: . Return `setResult(...)` and `finish()`. IMPORTANT: For payment/wallet trust decisions, treat only `getCallingPackage()` as verified identity. -Do not rely on referrer extras for caller authentication because callers can spoof those values. +Don't rely on referrer extras for caller authentication because callers can spoof those values. This implies the contract should be launched with `startActivityForResult(...)` so caller identity is available. [sidebar] @@ -1914,20 +1919,20 @@ This implies the contract should be launched with `startActivityForResult(...)` **** Use this checklist when the integration can activate payment credentials or accounts: -. **Fail closed on missing verified caller**. If `android.intent.caller` is empty (or `android.intent.caller.verified` is not `true`), reject the request and return a failure/cancel result. + +. **Fail closed on missing verified caller**. If `android.intent.caller` is empty (or `android.intent.caller.verified` isn't `true`), reject the request and return a failure/cancel result. + _Where_: CN1 `start()` guard before any business logic. . **Use an allow-list** of trusted caller package names and reject everything else. + _Where_: CN1 `start()` (or native bridge) before network calls. -. **Verify signing certificate fingerprints natively** for each allowed package. Package name checks alone are not sufficient in high-trust wallet scenarios. + +. **Verify signing certificate fingerprints natively** for each allowed package. Package name checks alone aren't enough in high-trust wallet scenarios. + _Where_: native Android code (bridge activity/native interface), because this requires Android `PackageManager` APIs. -. **Validate payload freshness** (nonce/timestamp/challenge) before approving. + - _Where_: backend/API service as the source of truth. +. **Check payload freshness** (nonce/timestamp/challenge) before approving. + + _Where_: back end/API service as the source of truth. . **Bind response to request** (request id / correlation id) and reject mismatched or replayed responses. + - _Where_: backend first, then enforce in CN1/native completion flow. + _Where_: back end first, then enforce in CN1/native completion flow. . **Time-box bridge activity state** and fail closed on timeout, process death, or missing bridge instance. + _Where_: native Android bridge + CN1 timeout handling. . **Audit log** caller package, request id, decision, and failure reason (without storing full PAN/token data). + - _Where_: backend and security telemetry pipeline. + _Where_: back end and security telemetry pipeline. **** Optional native Android example for certificate fingerprint verification (called from bridge/native layer before trusting caller): @@ -2034,7 +2039,7 @@ public void start() { return; } - // Run authentication + backend verification, then call native bridge completion API. + // Run authentication + back end verification, then call native bridge completion API. return; } showMainForm(); @@ -2064,7 +2069,7 @@ iOS is practically identical to Android with some small caveats, iOS's equivalen You can inject more data into the plist by using the `ios.plistInject` build hint. -So the equivalent in the iOS side would be +the equivalent in the iOS side would be [source,xml] ---- @@ -2079,11 +2084,11 @@ ios.urlScheme=myapp ---- [[native-peer-components]] -=== Native Peer Components +=== Native peer components -Many Codename One developers don't truly grasp the reason for the separation between peer (native) components and Codename One components. This is a crucial thing you need to understand especially if you plan on working with native widgets for example: Web Browser, native maps, text input, media and native interfaces (which can return a `PeerComponent`). +Many Codename One developers don't truly grasp the reason for the separation between peer (native) components and Codename One components. This is a crucial thing you need to understand if you plan on working with native widgets for example: Web Browser, native maps, text input, media and native interfaces (which can return a `PeerComponent`). -Codename One draws all of its widgets on its own, this is a concept which was modeled in part after Swing. This +Codename One draws all its widgets on its own, this is a concept which was modeled in part after Swing. This allows functionality that can't be achieved in native widget platforms: . The Codename One GUI builder & simulator are almost identical to the device - notice that this also enables the build cloud, otherwise device specific bugs would overwhelm development and make the build cloud redundant. @@ -2091,21 +2096,21 @@ allows functionality that can't be achieved in native widget platforms: . Ability to override everything - paint, pointer, key events are all overridable and replaceable. Developers can also paint over everything for example: glasspane and layered pane. -. Consistency - provides identical functionality on all platforms for the most part. +. Consistency - provides identical functionality on all platforms. This all contributes to your ease of working with Codename One and maintaining Codename One. More than 95% of Codename One's code is in Java hence its portable and pretty easy to maintain! -==== Why does Codename One Need Native Widgets at all? +==== Why does Codename One need native widgets at all You need the native device to do input, html rendering etc. these are too big and too complex tasks for Codename One to do from scratch. They are sometimes impossible to perform without the native platform. For example: the virtual keyboard input on the devices is tied directly to the native text input. it's impractical to implement everything from scratch for all languages, dictionaries etc. The result would be sub-par. -A web browser can't be implemented in this day and age without a JavaScript JIT and including a JIT within an iOS app is prohibited by Apple. +A web browser can't be implemented in this age without a JavaScript JIT and including a JIT within an iOS app is prohibited by Apple. -==== So what's the problems with native widgets? +==== So what's the problems with native widgets Codename One does pretty much everything on the EDT (Event Dispatch Thread), this provides a lot of cool features for example: modal dialogs, invokeAndBlock etc. @@ -2121,21 +2126,21 @@ This means that all peer components are drawn on top of the Codename One compone NOTE: This was also the case in AWT/Swing to one degree or another... -==== So how do you show dialogs on top of Peer Components? +==== So how do you show dialogs on top of peer components -Codename One grabs a screenshot of the peer, hide it and then you can show the screenshot. Since the screenshot is static it can be rendered through the standard UI. Naturally you can't do that always since grabbing a screenshot is an expensive process on all platforms and must be performed on the native device thread. +Codename One grabs a screenshot of the peer, hide it and then you can show the screenshot. Since the screenshot is static it can be rendered through the standard UI. You can't do that always since grabbing a screenshot is an expensive process on all platforms and must be performed on the native device thread. -==== Why can't you combine peer component scrolling and Codename One scrolling? +==== Why can't you combine peer component scrolling and Codename One scrolling Since the form title/footer etc. are drawn by Codename One the peer component might paint itself on top of them. Clipping a peer component is often pretty difficult. Furthermore, if the user drags his finger within the peer component he might trigger the native scroll within the might collide with your scrolling? -==== Native Components In The First Form +==== Native components in the first Form There is also another problem that might be counter intuitive. iOS has screenshot images representing the first form. If your first page is an HTML or a native map (or other peer widget) the screenshot process on the build server will show fallback code instead of the real thing thus providing sub-par behavior. -Its impractical to support something like HTML for the screenshot process since it would also look completely different from the web component running on the device. +Its impractical to support something like HTML for the screenshot process since it would also look different from the web component running on the device. // HTML_ONLY_START TIP: You can read more about the screenshot process https://www.codenameone.com/manual/appendix-ios.html#section-ios-screenshots[here]. @@ -2146,10 +2151,10 @@ TIP: You can read more about the screenshot process <` section of the manifest. -On iOS, this may mean specifying additional core frameworks for inclusion, or adding build flags for compilation. +On iOS, this may mean specifying more core frameworks for inclusion, or adding build flags for compilation. The following diagram shows the dependencies in a native library: @@ -2211,43 +2216,43 @@ In the specific case of your FreshDesk API, the public API and classes will look .Freshdesk API Integration image::img/5fe88406-61e4-11e5-951e-e09bd28a93c9.png[Freshdesk API Integration,scaledwidth=30%] -===== Things to Notice +===== Things to notice 1. The public API consists of the main class (https://github.com/shannah/cn1-freshdesk/blob/master/cn1-freshdesk-demo/src/com/codename1/freshdesk/Mobihelp.java[`Mobihelp`]), and a few supporting classes (https://github.com/shannah/cn1-freshdesk/blob/master/cn1-freshdesk-demo/src/com/codename1/freshdesk/FeedbackRequest.java[`FeedbackRequest`], https://github.com/shannah/cn1-freshdesk/blob/master/cn1-freshdesk-demo/src/com/codename1/freshdesk/FeedbackType.java[`FeedbackType`], https://github.com/shannah/cn1-freshdesk/blob/master/cn1-freshdesk-demo/src/com/codename1/freshdesk/MobihelpConfig.java[`MobihelpConfig`], https://github.com/shannah/cn1-freshdesk/blob/master/cn1-freshdesk-demo/src/com/codename1/freshdesk/MobihelpCallbackStatus.java[`MobihelpCallbackStatus`]), which were copied almost directly from the Android SDK. 2. The way for the public API to communicate with the native SDK is through the https://github.com/shannah/cn1-freshdesk/blob/master/cn1-freshdesk-demo/src/com/codename1/freshdesk/MobihelpNative.java[`MobihelpNative`] interface. -3. You introduced the https://github.com/shannah/cn1-freshdesk/blob/master/cn1-freshdesk-demo/src/com/codename1/freshdesk/MobihelpNativeCallback.java[`MobihelpNativeCallback`] class to facilitate native code calling back into the public API. This was necessary for a few methods that used asynchronous callbacks. +3. You introduced the https://github.com/shannah/cn1-freshdesk/blob/master/cn1-freshdesk-demo/src/com/codename1/freshdesk/MobihelpNativeCallback.java[`MobihelpNativeCallback`] class to ease native code calling back into the public API. This was necessary for a few methods that used asynchronous callbacks. -==== Step 4: Implement the Public API and Native Interface +==== Step 4: implement the public API and native interface you've already looked at the final product of the public API in the previous step, but let's back up and walk through the process step-by-step. -I wanted to model my API closely around the Android API, and the central class that includes all of the functionality of the SDK is the http://developer.freshdesk.com/mobihelp/android/api/reference/com/freshdesk/mobihelp/Mobihelp.html[com.freshdesk.mobihelp.Mobihelp class], so you begin there. +I wanted to model your API around the Android API, and the central class that includes all the functionality of the SDK is the http://developer.freshdesk.com/mobihelp/android/api/reference/com/freshdesk/mobihelp/Mobihelp.html[com.freshdesk.mobihelp.Mobihelp class], so you begin there. You will start by creating your own package (`com.codename1.freshdesk`) and your own `Mobihelp` class inside it. -===== Adapting Method Signatures +===== Adapting method signatures ====== The `Context` parameter -In a first glance at the http://developer.freshdesk.com/mobihelp/android/api/reference/com/freshdesk/mobihelp/Mobihelp.html[com.freshdesk.mobihelp.Mobihelp API] you see that many of the methods take a parameter of type http://developer.android.com/reference/android/content/Context.html[`android.content.Context`]. This class is part of the core Android SDK, and won't be accessible to any pure Codename One APIs. Therefore, your public API can't include any such references. Luckily, you will be able to access a suitable context in the native layer, so you will omit this parameter from your public API, and inject them in your native implementation. +In a first glance at the http://developer.freshdesk.com/mobihelp/android/api/reference/com/freshdesk/mobihelp/Mobihelp.html[com.freshdesk.mobihelp.Mobihelp API] you see that many of the methods take a parameter of type http://developer.android.com/reference/android/content/Context.html[`android.content.Context`]. This class is part of the core Android SDK, and won't be accessible to any pure Codename One APIs. So, your public API can't include any such references., you will be able to access a suitable context in the native layer, so you will omit this parameter from your public API, and inject them in your native implementation. -Hence, the method signature `public static final void setUserFullName (Context context, String name)` will simply become `public static final void setUserFullName (String name)` in your public API. +Hence, the method signature `public static final void setUserFullName (Context context, String name)` will become `public static final void setUserFullName (String name)` in your public API. -====== Non-Primitive Parameters +====== Non-Primitive parameters -Although your public API isn't constrained by the same rules as your Native Interfaces with respect to parameter and return types, you need to be cognizant of the fact that parameters you pass to your public API will ultimately be funnelled through your native interface. Therefore, you should pay attention to any parameters or return types that can't be passed directly to a native interface, and start forming a strategy for them. For example: consider the following method signature from the Android `Mobihelp` class: +Although your public API isn't constrained by the same rules as your Native Interfaces about parameter and return types, you need to be cognizant of the fact that parameters you pass to your public API will be funnelled through your native interface. So, you should pay attention to any parameters or return types that can't be passed directly to a native interface, and start forming a strategy for them. For example: consider the following method signature from the Android `Mobihelp` class: ---- public static final void showSolutions (Context activityContext, ArrayList tags) ---- -You've already decided to omit the `Context` parameter in your API, so that's a non-issue. But what about the `ArrayList` tags parameter? Passing this to your public API is no problem, but when you implement the public API, how will you pass this `ArrayList` to your native interface, since native interfaces don't allow you to arrays of strings as parameters? +You've already decided to omit the `Context` parameter in your API, so that's a non-issue. However, what about the `ArrayList` tags parameter? Passing this to your public API is no problem, but when you implement the public API, how will you pass this `ArrayList` to your native interface, since native interfaces don't allow you to arrays of strings as parameters? -I generally use one of three strategies in such cases: +I use one of three strategies in such cases: -. Encode the parameter as either a single `String` (for example: using JSON or some other easily parseable format) or a byte[] array (in some known format that can easily be parsed in native code). +. Encode the parameter as either a single `String` (for example: using JSON or some other parseable format) or a byte[] array (in some known format that can be parsed in native code). . Store the parameter on the Codename One side and pass some ID or token that can be used on the native side to retrieve the value. -. If the data structure can be expressed as a finite number of primitive values, then simply design the native interface method to take the individual values as parameters instead of a single object. For example: If there is a https://www.codenameone.com/javadoc/com/codename1/facebook/User.html[User] class with properties `name` and `phoneNumber`, the native interface can have `name` and `phoneNumber parameters rather than a single `user` parameter. +. If the data structure can be expressed as a finite number of primitive values, then design the native interface method to take the individual values as parameters instead of a single object. For example: If there is a https://www.codenameone.com/javadoc/com/codename1/facebook/User.html[User] class with properties `name` and `phoneNumber`, the native interface can have `name` and `phoneNumber parameters rather than a single `user` parameter. In this case, because an array of strings is such a simple data structure, I decided to use a variation on strategy number 1: Merge the array into a single string with a delimiter. @@ -2257,9 +2262,9 @@ In any case, you don't have to come up with the specifics right now, as you're s it's often the case that native code needs to call back into Codename One code when an event occurs. This may be connected directly to an API method call (for example: as the result of an asynchronous method invocation), or due to something initiated by the operating system or the native SDK on its own (for example: a push notification, a location event, etc..). -Native code will have access to both the Codename One API and any native APIs in your app, but on some platforms, accessing the Codename One API may be a little tricky. For example: on iOS you'll be calling from Objective-C back into Java which requires knowledge of Codename One's java-to-objective C conversion process. In general, I have found that the easiest way to facilitate callbacks is to provide abstractions that involve static java methods (in Codename One space) that accept and return primitive types. +Native code will have access to both the Codename One API and any native APIs in your app, but on some platforms, accessing the Codename One API may be a little tricky. For example: on iOS you'll be calling from Objective-C back into Java which requires knowledge of Codename One's java-to-goal C conversion process. In general, I have found that the easiest way to ease callbacks is to provide abstractions that involve static Java methods (in Codename One space) that accept and return primitive types. -In the case of your `Mobihelp` class, the following method hints at the need to have a "callback plan": +For your `Mobihelp` class, the following method hints at the need to have a "callback plan": ---- public static final void getUnreadCountAsync (Context context, UnreadUpdatesCallback callback) @@ -2274,12 +2279,12 @@ include::../demos/common/src/main/java/com/codenameone/developerguide/advancedto that's: If you were to implement this method (which I plan to do), you need to have a way for the native code to call the `callback.onResult()` method of the passed parameter. -So you've two issues that will need to be solved here: +you've two issues that will need to be solved here: 1. How to pass the `callback` object through the native interface. 2. How to *call* the `callback.onResult()` method from native code at the right time. -For the first issue, you will use strategy #2 that you mentioned previously: (Store the parameter on the Codename One side and pass some ID or token that can be used on the native side to retrieve the value). +For the first issue, you will use strategy #2 that you mentioned earlier: (Store the parameter on the Codename One side and pass some ID or token that can be used on the native side to retrieve the value). For the second issue, you will create a static method that can take the token generated to solve the first issue, and call the stored `callback` object's `onResult()` method. You abstract both sides of this process using the https://github.com/shannah/cn1-freshdesk/blob/master/cn1-freshdesk-demo/src/com/codename1/freshdesk/MobihelpNativeCallback.java[`MobihelpNativeCallback` class]. @@ -2293,17 +2298,17 @@ include::../demos/common/src/main/java/com/codenameone/support/mobihelp/UnreadUp 1. This class uses a static `Map` member to keep track of all callbacks, mapping a unique integer ID to each callback. 2. The `registerUnreadUpdatesCallback()` method takes an `UnreadUpdatesCallback` object, places it in the `callbacks` map, and returns the integer *token* that can be used to fire the callback later. This method would be called by the public API inside the `getUnreadCountAsync()` method implementation to convert the `callback` into an integer, which can then be passed to the native API. 3. The `fireUnreadUpdatesCallback()` method would be called later from native code. Its first parameter is the token for the callback to call. -4. You wrap the `onResult()` call inside a `Display.callSerially()` invocation to ensure that the callback is called on the EDT. This is a general convention that's used throughout Codename One, and you'd be well-advised to follow it. *Event handlers* should be run on the EDT unless there is a good reason not to - and in that case your documentation and naming conventions should make this clear to avoid accidentally stepping into multithreading hell! +4. You wrap the `onResult()` call inside a `Display.callSerially()` invocation to ensure that the callback is called on the EDT. This is a general convention that's used throughout Codename One, and you'd be well-advised to follow it. *Event handlers* should be run on the EDT unless there is a good reason not to - and in that case your documentation and naming conventions should make this clear to avoid stepping into multithreading hell! ===== Initialization -Most Native SDKs include some sort of initialization method where you pass your developer and application credentials to the API. When I filled in FreshDesk's web-based form to create a new application, it generated an application ID, an app "secret", and a "domain". The SDK requires me to pass all three of these values to its `init()` method through the `MobihelpConfig` class. +Most Native SDKs include some sort of initialization method where you pass your developer and application credentials to the API. When I filled in FreshDesk's web-based form to create a new application, it generated an application ID, an app "secret," and a "domain." The SDK requires you to pass all three of these values to its `init()` method through the `MobihelpConfig` class. -Note, however, that FreshDesk (and most other service provides that have native SDKs) requires me to create different Apps for each platform. This means that my App ID and App secret will be different on iOS than they will be on Android. +Note, but, that FreshDesk (and most other service provides that have native SDKs) requires you to create different Apps for each platform. This means that your App ID and App secret will be different on iOS than they will be on Android. -Therefore your public API needs to enable you to provide many credentials in the same app, and your API needs to know to use the correct credentials depending on the device that the app is running on. +So your public API needs to enable you to provide many credentials in the same app, and your API needs to know to use the correct credentials depending on the device that the app is running on. -There are many solutions to this problem, but the one I chose was to provide two different `init()` methods: +many solutions to this problem, but the one I chose was to provide two different `init()` methods: [source,java] ---- @@ -2324,16 +2329,16 @@ Then I can set up the API with code like: include::../demos/common/src/main/java/com/codenameone/support/mobihelp/Mobihelp.java[tag=mobihelpInitAndroid,indent=0] ---- -===== The Resulting Public API +===== The resulting public API [source,java] ---- include::../demos/common/src/main/java/com/codenameone/support/mobihelp/MobihelpUsage.java[tag=mobihelpUsage,indent=0] ---- -===== The Native Interface +===== The native interface -The final native interface is nearly identical to your public API, except in cases where the public API included non-primitive parameters. +The final native interface is identical to your public API, except in cases where the public API included non-primitive parameters. [source,java] ---- @@ -2342,9 +2347,9 @@ include::../demos/common/src/main/java/com/codenameone/support/mobihelp/Mobihelp Notice also, that the native interface includes a set of methods with names prefixed with `config__`. This is a naming conventions I used to identify methods that map to the `MobihelpConfig` class. I could have used a separate native interface for these, but decided to keep all the native stuff in one class for simplicity and maintainability. -===== Connecting the Public API to the Native Interface +===== Connecting the public API to the native interface -So you've a public API, and you've a native interface. The idea is that the public API should be a thin wrapper around the native interface to smooth out rough edges that are likely to exist due to the strict set of rules involved in native interfaces. You will, therefore, use delegation inside the `Mobihelp` class to provide it a reference to an instance of `MobihelpNative`: +you've a public API, and you've a native interface. The idea is that the public API should be a thin wrapper around the native interface to smooth out rough edges that are likely to exist due to the strict set of rules involved in native interfaces. You will, so, use delegation inside the `Mobihelp` class to provide it a reference to an instance of `MobihelpNative`: [source,java] ---- @@ -2360,10 +2365,10 @@ include::../demos/common/src/main/java/com/codenameone/support/mobihelp/Mobihelp *Things to Notice*: -1. The `initAndroid()` and `initIOS()` methods include a check to see if they are running on the correct platform. Ultimately they both call `init()`. +1. The `initAndroid()` and `initIOS()` methods include a check to see if they are running on the correct platform. They both call `init()`. 2. The `init()` method, uses the https://www.codenameone.com/javadoc/com/codename1/system/NativeLookup.html[NativeLookup] class to instantiate your native interface. -===== Implementing the Glue Between Public API and Native Interface +===== Implementing the glue between public API and native interface For most of the methods in the `Mobihelp` class, you can see that the public API will be a thin wrapper around the native interface. For example: the public API implementation of `setUserFullName(String)` is: @@ -2388,9 +2393,9 @@ The other non-trivial wrapper is the `getUnreadCountAsync()` method that you dis include::../demos/common/src/main/java/com/codenameone/support/mobihelp/Mobihelp.java[tag=mobihelpShowSupportWithTags,indent=0] ---- -==== Step 5: Implementing the Native Interface in Android +==== Step 5: implementing the native interface in Android -Now that you've set up your public API and your native interface, it's time to work on the native side of things. You can generate stubs for all platforms in your IDE (Netbeans in my case), by right clicking on the `MobihelpNative` class in the project explorer and selecting "Generate Native Access". +Now that you've set up your public API and your native interface, it's time to work on the native side of things. You can generate stubs for all platforms in your IDE (Netbeans in your case), by right clicking on the `MobihelpNative` class in the project explorer and selecting `Generate Native Access`. [[c9d4b9cc-61f6-11e5-8b67-4691600188cd]] .Generate Native Access Menu Item @@ -2415,13 +2420,13 @@ Your implementation will be a thin wrapper around the native Android SDK. See th include::../demos/common/src/main/java/com/codenameone/support/mobihelp/Mobihelp.java[tag=mobihelpGetUnreadCountAsync,indent=0] ---- + -This will enable you to easily wrap the freshdesk native API. For example: +This will enable you to wrap the freshdesk native API. For example: + [source,java] ---- include::../demos/android/src/main/java/com/codenameone/support/mobihelp/MobihelpNativeImpl.java[tag=mobihelpNativeContext,indent=0] ---- -2. `runOnUiThread()` - Many of the calls to the FreshDesk API may have been made from the Codename One EDT. For example, Android has its own event dispatch thread that should be used for interacting with native Android UI. Therefore, any API calls that look like they initiate some sort of native Android UI process should be wrapped inside Android's `runOnUiThread()` method which is similar to Codename One's `Display.callSerially()` method. For example: see the `showSolutions()` method: +2. `runOnUiThread()` - Many of the calls to the FreshDesk API may have been made from the Codename One EDT. For example, Android has its own event dispatch thread that should be used for interacting with native Android UI. So, any API calls that look like they start some sort of native Android UI process should be wrapped inside Android's `runOnUiThread()` method which is like Codename One's `Display.callSerially()` method. For example: see the `showSolutions()` method: + [source,java] ---- @@ -2436,17 +2441,17 @@ include::../demos/android/src/main/java/com/codenameone/support/mobihelp/Mobihel include::../demos/android/src/main/java/com/codenameone/support/mobihelp/MobihelpNativeImpl.java[tag=mobihelpNativeShowSolutions,indent=0] ---- -==== Step 6: Bundling the Native SDKs +==== Step 6: bundling the native SDKs The last step (at least on the Android side) is to bundle the FreshDesk SDK. For Android, there are a few different scenarios you'll run into for embedding SDKs: . *The SDK includes Java classes* - NO XML UI files, assets, or resources that aren't included inside a simple.jar file. In this case, you can place the.jar file inside your project's `native/android` directory. -. *The SDK includes some XML UI files, resources, and assets.* In this case, the SDK is generally distributed as an Android project folder that can be imported into an Eclipse or Android studio workspace. In general, in this case, you would need to zip the entire directory and change the extension of the resulting.zip file to ".andlib", and place this in your project's `native/android` directory. +. *The SDK includes some XML UI files, resources, and assets.* In this case, the SDK is distributed as an Android project folder that can be imported into an Eclipse or Android studio workspace. In general, in this case, you would need to zip the entire directory and change the extension of the resulting.zip file to `.andlib`, and place this in your project's `native/android` directory. . *The SDK is distributed as an `.aar` file* - In this case you can copy the `.aar` file into your `native/android` directory. ===== The FreshDesk SDK -The FreshDesk (aka Mobihelp) SDK is distributed as a project folder (that's: scenario 2 from the above list). Therefore, your procedure is to download the SDK (https://s3.amazonaws.com/assets.mobihelp.freshpo.com/sdk/mobihelp_sdk_android.zip[download link]), and rename it from `mobihelp_sdk_android.zip` to `mobihelp_sdk_android.andlib`, and copy it into your `native/android` directory. +The FreshDesk (aka Mobihelp) SDK is distributed as a project folder (that's: scenario 2 from the above list). So, your procedure is to download the SDK (https://s3.amazonaws.com/assets.mobihelp.freshpo.com/sdk/mobihelp_sdk_android.zip[download link]), and rename it from `mobihelp_sdk_android.zip` to `mobihelp_sdk_android.andlib`, and copy it into your `native/android` directory. ===== Dependencies @@ -2460,9 +2465,9 @@ If you look inside the `project.properties` file (inside the Mobihelp SDK direct android.library.reference.1=../appcompat_v7 ---- -that's: it's expecting to find the `appcompat_v7` library located in the same parent directory as the Mobihelp SDK project. After a little bit of research (if you're not yet familiar with the Android AppCompat support library), you find that the `AppCompat_v7` library is part of the Android Support library, which can can installed into your local Android SDK using Android SDK Manager. https://developer.android.com/tools/support-library/setup.html[Installation processed specified here]. +that's: it's expecting to find the `appcompat_v7` library located in the same parent directory as the Mobihelp SDK project. After a little bit of research (if you're not yet familiar with the Android AppCompat support library), you find that the `AppCompat_v7` library is part of the Android Support library, which can installed into your local Android SDK using Android SDK Manager. https://developer.android.com/tools/support-library/setup.html[Installation processed specified here]. -After installing the support library, you need to retrieve it from your Android SDK. You can find that.aar file inside the `ANDROID_HOME/sdk/extras/android/m2repository/com/android/support/appcompat-v7/19.1.0/` directory (for version 19.1.0). The contents of that directory on my system are: +After installing the support library, you need to retrieve it from your Android SDK. You can find that.aar file inside the `ANDROID_HOME/sdk/extras/android/m2repository/com/android/support/appcompat-v7/19.1.0/` directory (for version 19.1.0). The contents of that directory on your system are: ---- appcompat-v7-19.1.0.aar appcompat-v7-19.1.0.pom @@ -2470,9 +2475,9 @@ appcompat-v7-19.1.0.aar.md5 appcompat-v7-19.1.0.pom.md5 appcompat-v7-19.1.0.aar.sha1 appcompat-v7-19.1.0.pom.sha1 ---- -There are two files of interest here: +two files of interest here: -. appcompat-v7-19.1.0.aar - This is the actual library that you need to include in your project to satisfy the Mobisdk dependency. +. appcompat-v7-19.1.0.aar - This is the actual library that you need to include in your project to meet the Mobisdk dependency. . appcompat-v7-19.1.0.pom - This is the Maven XML file for the library. It will show you any dependencies that the appcompat library has. You will also need to include these dependencies: + ---- @@ -2508,9 +2513,9 @@ support-v4-19.1.0-sources.jar.md5 support-v4-19.1.0.pom.md5 support-v4-19.1.0-sources.jar.sha1 support-v4-19.1.0.pom.sha1 ---- + -Looks like this library is pure Java classes, so you need to include the `support-v4-19.1.0.jar` file into your project. Checking the `.pom` file you see that there are no additional dependencies you need to add. +Looks like this library is pure Java classes, so you need to include the `support-v4-19.1.0.jar` file into your project. Checking the `.pom` file you see that there are no more dependencies you need to add. -So, to summarize your findings, you need to include the following files in your `native/android` directory: +to summarize your findings, you need to include the following files in your `native/android` directory: . appcompat-v7-19.1.0.aar . support-v4-19.1.0.jar @@ -2524,7 +2529,7 @@ appcompat_v7.aar mobihelp.andlib com support-v4-19.1.0.jar ---- -==== Step 7 : Injecting Android Manifest and Proguard Config +==== Step 7 : injecting Android manifest and proguard config The final step on the Android side is to inject necessary permissions and services into the project's AndroidManifest.xml file. @@ -2622,12 +2627,12 @@ You can find the manifest file injections required by opening the `AndroidManife ---- -You will need to add the `` tags and all of the contents of the `` tag to your manifest file. Codename One provides the following build hints for these: +You will need to add the `` tags and all the contents of the `` tag to your manifest file. Codename One provides the following build hints for these: . `android.xpermissions` - For your `` directives. Add a build hint with name `android.xpermissions`, and for the value, paste the actual `` XML tag. . `android.xapplication` - For the contents of your `` tag. -===== Proguard Config +===== Proguard config For the release build, you will also need to inject some proguard configuration so that important classes don't get stripped out at build time. The FreshDesk SDK instructions state: @@ -2639,7 +2644,7 @@ For the release build, you will also need to inject some proguard configuration > -keep class android.support.v7.** { *; } > ---- -In addition, if you look at the `proguard-project.txt` file inside the Mobihelp SDK, you'll see the rules: +Also, if you look at the `proguard-project.txt` file inside the Mobihelp SDK, you'll see the rules: ---- -keep public class * extends android.app.Service @@ -2655,12 +2660,12 @@ In addition, if you look at the `proguard-project.txt` file inside the Mobihelp You will want to merge this and then paste them into the build hint `android.proguardKeep` of your project. -===== Troubleshooting Android Stuff +===== Troubleshooting Android stuff If, after doing all this, your project fails to build, you can enable the "Include Source" option of the build server, then download the source project, open it in Eclipse or Android Studio, and debug from there. -=== Part 2: Implementing the iOS Native Code +=== Part 2: implementing the iOS native code Part 1 of this tutorial focused on the Android native integration. Now you will shift your focus to the iOS implementation. @@ -2681,8 +2686,8 @@ You more-or-less follow the http://developer.freshdesk.com/mobihelp/ios/integrat ---- include::../demos/ios/src/main/objectivec/com_codename1_freshdesk_MobihelpNativeImpl.m[tag=mobihelpImport,indent=0] ---- -. Similar to your use of `runOnUiThread()` in Android, you will wrap all of your API calls in either `dispatch_async()` or `dispatch_sync()` calls to ensure that you're interacting with the Mobihelp API on the app's main thread rather than the Codename One EDT. -. Some methods/messages in the Mobihelp SDK require you to pass a `UIViewController` as a parameter. In Codename One, the entire application uses a single UIViewController: `CodenameOne_GLViewController`. You can obtain a reference to this using the `[CodenameOne_GLViewController instance]` message. You need to import its header file: +. Like your use of `runOnUiThread()` in Android, you will wrap all your API calls in either `dispatch_async()` or `dispatch_sync()` calls to ensure that you're interacting with the Mobihelp API on the app's main thread rather than the Codename One EDT. +. Some methods/messages in the Mobihelp SDK require you to pass a `UIViewController` as a parameter. In Codename One, the entire application uses a single UIViewController: `CodenameOne_GLViewController`. You can get a reference to this using the `[CodenameOne_GLViewController instance]` message. You need to import its header file: + ---- include::../demos/ios/src/main/objectivec/com_codename1_freshdesk_MobihelpNativeImpl.m[tag=mobihelpControllerImport,indent=0] @@ -2696,7 +2701,7 @@ include::../demos/ios/src/main/objectivec/com_codename1_freshdesk_MobihelpNative ==== Using the MobihelpNativeCallback -You described earlier how you created a static method on the `MobihelpNativeCallback` class so that native code could easily fire a callback method. Now let's take a look at how this looks from the iOS side of the fence. Here is the implementation of `getUnreadCountAsync()`: +You described earlier how you created a static method on the `MobihelpNativeCallback` class so that native code could fire a callback method. Now let's take a look at how this looks from the iOS side of the fence. Here is the implementation of `getUnreadCountAsync()`: ---- include::../demos/ios/src/main/objectivec/com_codename1_freshdesk_MobihelpNativeImpl.m[tag=mobihelpGetUnreadCountAsync,indent=0] @@ -2720,22 +2725,22 @@ com_codename1_freshdesk_MobihelpNativeCallback_fireUnreadUpdatesCallback___int_i include::../demos/ios/src/main/objectivec/com_codename1_freshdesk_MobihelpNativeImpl.m[tag=mobihelpCallbackImport,indent=0] ---- -==== Bundling Native iOS SDK +==== Bundling native iOS SDK -Now that you've implemented your iOS native interface, you need to bundle the Mobihelp iOS SDK into your project. There are a few different scenarios you may face when looking to include a native SDK: +Now that you've implemented your iOS native interface, you need to bundle the Mobihelp iOS SDK into your project. a few different scenarios you may face when looking to include a native SDK: . The SDK includes `.bundle` resource files. In this case, copy the `.bundle` file(s) into your `native/ios` directory. . The SDK includes `.h` header files. In this case, copy the `.h` file(s) into your `native/ios` directory. . The SDK includes `.a` files. In this case, copy the `.a` file(s) into your `native/ios` directory. . The SDK includes `.framework` files. In this case, you'll need to zip up the framework, and copy it into your `native/ios` directory. For example: If the framework is named, MyFramework.framework, then the zip file should be named MyFramework.framework.zip, and should be located at `native/ios/MyFramework.framework.zip`. -The FreshDesk SDK doesn't include any `.framework` files, so you don't need to worry about that last scenario. You simply https://s3.amazonaws.com/assets.mobihelp.freshpo.com/sdk/mobihelp_sdk_ios.zip[download the iOS SDK], copy the `libFDMobihelpSDK.a`, `Mobihelp.h`. `MHModel.bundle`, `MHResources.bundle`, and `MHLocalization/en.proj/MHLocalizable.strings` into `native/ios`. +The FreshDesk SDK doesn't include any `.framework` files, so you don't need to worry about that last scenario. You https://s3.amazonaws.com/assets.mobihelp.freshpo.com/sdk/mobihelp_sdk_ios.zip[download the iOS SDK], copy the `libFDMobihelpSDK.a`, `Mobihelp.h`. `MHModel.bundle`, `MHResources.bundle`, and `MHLocalization/en.proj/MHLocalizable.strings` into `native/ios`. ==== Troubleshooting iOS -If you run into problems with the build, you can select "Include Sources" in the build server to download the resulting Xcode Project. You can then debug the Xcode project locally, make changes to your iOS native implementation files, and copy them back into your project once it's building properly. +If you run into problems with the build, you can select "Include Sources" in the build server to download the resulting Xcode Project. You can then debug the Xcode project locally, make changes to your iOS native implementation files, and copy them back into your project once it's building. -==== Adding Required Core Libraries and Frameworks +==== Adding required core libraries and frameworks The iOS integration guide for the FreshDesk SDK lists the following core frameworks as dependencies: @@ -2749,31 +2754,31 @@ You can add these dependencies to your project using the `ios.add_libs` build hi .iOS's "add libs" build hint image::img/65e31df8-620c-11e5-87ff-6b926a3f2090.png[iOS's "add libs" build hint,scaledwidth=30%] -that's: you list the framework names separated by semicolons. Notice that my list in the above image doesn't include all of the frameworks that they list because many of the frameworks are already included by default (I obtained the default list by simply building the project with "include sources" checked, then looked at the frameworks that were included). +that's: you list the framework names separated by semicolons. Notice that your list in the above image doesn't include all the frameworks that they list because many of the frameworks are already included by default (I obtained the default list by building the project with "include sources" checked, then looked at the frameworks that were included). === Part 3 : Packaging as a cn1lib -During the initial development, I generally find it easier to use a regular Codename One project so that I can run and test as I go. But once it's stabilized, and I want to distribute the library to other developers, I will transfer it over to a Codename One library project. This general process involves: +During the initial development, I find it easier to use a regular Codename One project so that I can run and test as I go. However, once it's stabilized, and I want to distribute the library to other developers, I will transfer it over to a Codename One library project. This general process involves: . Create a Codename One Library project. -. Copy the.java files from my original project into the library project. +. Copy the.Java files from your original project into the library project. . Copy the `native` directory from the original project into the library project. . Copy the *relevant* build hints from the original project's `codenameone_settings.properties` file into the library project's `codenameone_library_appended.properties` file. -In the case of the FreshDesk.cn1lib, I modified the original project's build script to generate and build a library project automatically. But that's beyond the scope of this tutorial. +For the FreshDesk.cn1lib, I modified the original project's build script to generate and build a library project automatically. However, that's beyond the scope of this tutorial. -=== Building Your Own Layout Manager +=== Building your own Layout manager -A https://www.codenameone.com/javadoc/com/codename1/ui/layouts/Layout.html[Layout] contains all the logic for positioning Codename One components. It essentially traverses a Codename One https://www.codenameone.com/javadoc/com/codename1/ui/Container.html[Container] and positions components absolutely based on internal logic. +A https://www.codenameone.com/javadoc/com/codename1/ui/layouts/Layout.html[Layout] contains all the logic for positioning Codename One components. It essentially traverses a Codename One https://www.codenameone.com/javadoc/com/codename1/ui/Container.html[Container] and positions components based on internal logic. When you build the layout you need to take margin into consideration and make sure to add it into the position/size calculations. Building a layout manager involves two simple methods: `layoutContainer` & `getPreferredSize`. -`layoutContainer` is invoked whenever Codename One decides the container needs rearranging, Codename One tries to avoid calling this method and invokes it at the last possible moment. Since this method is generally expensive (imagine the recursion with nested layouts). Codename One marks a flag indicating layout is "dirty" when something important changes and tries to avoid "reflows". +`layoutContainer` is invoked whenever Codename One decides the container needs rearranging, Codename One tries to avoid calling this method and invokes it at the last possible moment. Since this method is expensive (imagine the recursion with nested layouts). Codename One marks a flag indicating layout is "dirty" when something important changes and tries to avoid "reflows." `getPreferredSize` allows the layout to determine the size desired for the container. This might be a difficult call to make for some layout managers that try to provide both flexibility and simplicity. -Most of `FlowLayout` bugs stem from the fact that this method is impossible to implement correctly & efficiently for all the use cases of a deeply nested `FlowLayout`. The size of the final layout won't necessarily match the requested size (it probably won't) but the requested size is taken into consideration, especially when scrolling and also when sizing parent containers. +Most of `FlowLayout` bugs stem from the fact that this method is impossible to implement & efficiently for all the use cases of a deeply nested `FlowLayout`. The size of the final layout won't necessarily match the requested size (it probably won't) but the requested size is taken into consideration, when scrolling and also when sizing parent containers. This is a layout manager that arranges components in a center column aligned to the middle. You then show the proper usage of margin to create a stair like effect with this layout manager: @@ -2785,44 +2790,44 @@ include::../demos/android/src/main/java/com/codenameone/support/mobihelp/Mobihel .Center layout staircase effect with margin image::img/center-layout.png[Center layout staircase effect with margin,scaledwidth=20%] -==== Porting a Swing/AWT Layout Manager +==== Porting a Swing/AWT Layout manager -The https://www.codenameone.com/javadoc/com/codename1/ui/layouts/GridBagLayout.html[GridBagLayout] was ported to Codename One relatively easily considering the complexity of that specific layout manager. Here are some tips you should take into account when porting a Swing/AWT layout manager: +The https://www.codenameone.com/javadoc/com/codename1/ui/layouts/GridBagLayout.html[GridBagLayout] was ported to Codename One considering the complexity of that specific layout manager. Here are some tips you should consider when porting a Swing/AWT layout manager: -. Codename One doesn't have `Insets`, you added some support for them in order to port GridBag but components in Codename One have a margin they need to consider instead of the `Insets` (the padding is in the preferred size and is thus hidden from the layout manager). +. Codename One doesn't have `Insets`, but adds some support for them to port GridBag but components in Codename One have a margin they need to consider instead of the `Insets` (the padding is in the preferred size and is thus hidden from the layout manager). . AWT layout managers also synchronize a lot on the AWT thread. This is no longer necessary since Codename One is single threaded, like Swing. . AWT considers the top left position of the `Container` to be 0,0 whereas Codename One considers the position based on its parent `Container`. The top left position in Codename One is `getX()`, `getY()`. -Other than those things it’s mostly fixing method and import statements, which are slightly different. Pretty trivial stuff. +Other than those things it’s fixing method and import statements, which are slightly different. Pretty trivial stuff. -=== Port a Language to Codename One +=== Port a language to Codename One As you may have already read, you've added support for Kotlin in Codename One. This is something that you can achieve without the help of Codename One. You could port a 3rd party language like Scala, Ruby, Python etc. to Codename One. -==== What is a JVM Language? +==== What is a JVM language A JVM Language is any programming language that can be compiled to byte-codes that will run on the JVM (Java Virtual Machine). Java was the original JVM language, but many others have sprung up over the years. https://kotlinlang.org/[Kotlin], https://www.scala-lang.org/[Scala], http://groovy-lang.org/[Groovy], and http://jruby.org/[JRuby] come to mind as well-established and mature languages, but there are https://en.wikipedia.org/wiki/List_of_JVM_languages[many others]. -==== How Hard is it to Port a JVM Language to Codename One? +==== How hard is it to port a JVM language to Codename One The difficulty of porting a particular language to Codename One will vary depending on such factors as: . Does it require a runtime library? -.. How complex is the runtime library? (For example: Does it require classes that aren't currently offered in Codename One's subset of the java standard libraries?) +.. How complex is the runtime library? (For example: Does it require classes that aren't offered in Codename One's subset of the java standard libraries?) . Does it need reflection? .. Codename One doesn't support reflection because it would result in a large application size. If a JVM language requires reflection to get off the ground then adding it to Codename one would be tricky. . Does it perform any runtime byte-code manipulation? .. Some dynamic languages may perform byte-code manipulation at runtime. This is problematic on iOS (and possibly other platforms) which prohibits such runtime behavior. -===== Step 1: Assess the Language +===== Step 1: assess the language The more similar a language, and its build outputs are to Java, the easier it will be to port (probably). Most JVM languages have two parts: 1. A compiler, which compiles source files to JVM byte-code (usually as.class files). 2. A runtime library. -Currently I am aware of one language (other than Java) that doesn't require a runtime library, and that's http://www.mirah.org/[Mirah]. +I am aware of one language (other than Java) that doesn't require a runtime library, and that's http://www.mirah.org/[Mirah]. NOTE: Codename One also supports https://www.codenameone.com/blog/mirah-for-codename-one.html[Mirah] @@ -2862,7 +2867,7 @@ class KotlinForm : Form { } ---- -Let you take a look at the bytecode that Kotlin produced for this class: +Let's take a look at the bytecode that Kotlin produced for this class: ---- $ javap -v com/codename1/hellokotlin2/KotlinForm.class @@ -3048,9 +3053,9 @@ Constant pool: ---- -The constant pool will consist of class names, and strings mostly. You'll want to peruse this list to see if the compiler has added any classes that aren't in the source code. In the example above, it looks like Kotlin is pretty faithful to the original source's dependencies. It didn't inject any classes that aren't in the original source. +The constant pool will consist of class names, and strings. You'll want to peruse this list to see if the compiler has added any classes that aren't in the source code. In the example above, it looks like Kotlin is pretty faithful to the original source's dependencies. It didn't inject any classes that aren't in the original source. -Even if the compiler does inject other dependencies into the bytecode, it might not be a problem. it's a problem if those classes aren't supported by Codename One. Keep your eyes peeled for anything in the `java.lang.reflect` package or unsolicited use of `java.net`, `java.nio`, or any other package that aren't part of the Codename One standard library. If you're not sure if a class or package is available in the Codename One standard library, check https://www.codenameone.com/javadoc/[the javadocs]. +Even if the compiler does inject other dependencies into the bytecode, it might not be a problem. it's a problem if those classes aren't supported by Codename One. Watch for anything in the `java.lang.reflect` package or unsolicited use of `java.net`, `java.nio`, or any other package that aren't part of the Codename One standard library. If you're not sure if a class or package is available in the Codename One standard library, check https://www.codenameone.com/javadoc/[the javadocs]. **The ByteCode Instructions**: @@ -3076,37 +3081,37 @@ public com.codename1.hellokotlin2.KotlinForm(); In the above snippet, the first instruction is `aload_0` (which adds `this` to the stack). The 2nd instruction is `ldc`, (which loads constant #8 -- the string "Hello Kotlin" to the stack). The 3rd instruction is `invokestatic` which calls the static method define by Constant #14 from the constant pool, with the two parameters that had been added to the stack. -NOTE: You don't need to understand what all of these instructions do. You need to look for instructions that may be problematic. +NOTE: You don't need to understand what all these instructions do. You need to look for instructions that may be problematic. -The instruction that I *think* might be problematic is "invokedynamic". All other instructions should work find in Codename One. (I don't know for a fact that invokedynmic won't work - I suspect it might not work on some platforms). +The instruction that I *think* might be problematic is "invokedynamic." All other instructions should work find in Codename One. (I don't know for a fact that invokedynmic won't work - I suspect it might not work on some platforms). **Summary of Byte-code Assessment** -So to summarize, the byte-code assessment phase, you're basically looking to make sure that the compiler doesn't tend to add dependencies to parts of the JDK that Codename One doesn't currently support. And you want to make sure that it doesn't use invokedynamic. +to summarize, the byte-code assessment phase, you're basically looking to make sure that the compiler doesn't tend to add dependencies to parts of the JDK that Codename One doesn't support. And you want to make sure that it doesn't use invokedynamic. If you find that the compiler does use invokedynamic or add references to classes that Codename One doesn't support, don't give up yet. You might be able to create your own "porting" runtime library that will provide these dependencies at runtime. -====== Assessing the Runtime Library +====== Assessing the runtime library -The process for assessing the runtime library is pretty similar to the process for the bytecodes. You'll want to get your hands on the language's runtime library, and use `javap` to inspect the.class files. You're looking for the same things as you were looking for in the compiler's output: "invokedynamic" and classes that aren't supported in Codename One. +The process for assessing the runtime library is pretty like the process for the bytecodes. You'll want to get your hands on the language's runtime library, and use `javap` to inspect the.class files. You're looking for the same things as you were looking for in the compiler's output: "invokedynamic" and classes that aren't supported in Codename One. -===== Step 2: Convert the Runtime Library into a CN1Lib +===== Step 2: convert the runtime library into a CN1Lib -Once you've assessed the language and are optimistic that it's a good candidate for porting, you can proceed to port the runtime library into Codename One. Usually that language's runtime library will be distributed in.jar format. You need to convert this into a cn1lib so that it can be used in a Codename One project. If you can get your hands on the source code for the runtime library then the best approach is to paste the source files into a Codename One Library project, and try to build it. This has the advantage that it will validate the source during compile to ensure that it doesn't depend on any classes that Codename One doesn't support. +Once you've assessed the language and are optimistic that it's a good candidate for porting, you can proceed to port the runtime library into Codename One. That language's runtime library will be distributed in.jar format. You need to convert this into a cn1lib so that it can be used in a Codename One project. If you can get your hands on the source code for the runtime library then the best approach is to paste the source files into a Codename One Library project, and try to build it. This has the advantage that it will check the source during compile to ensure that it doesn't depend on any classes that Codename One doesn't support. -If you can't find the sources of the runtime library or they don't seem to be easily "buildable", then the next best thing is to get the binary distribution's jar file and convert it to a cn1lib. This is what you did for the https://github.com/shannah/codenameone-kotlin[Kotlin runtime library]. +If you can't find the sources of the runtime library or they don't seem to be "buildable," then the next best thing is to get the binary distribution's jar file and convert it to a cn1lib. This is what you did for the https://github.com/shannah/codenameone-kotlin[Kotlin runtime library]. -This procedure exploits the fact that a cn1lib file is a zip file with a specific file structure inside it. The cross-platform Java.class files are all contained inside a file named "main.zip", inside the zip file. This is the *mandatory* file that must be inside a cn1lib. +This procedure exploits the fact that a cn1lib file is a zip file with a specific file structure inside it. The cross-platform Java.class files are all contained inside a file named `main.zip`, inside the zip file. This is the *mandatory* file that must be inside a cn1lib. -To make the library easier to use the cn1lib file can also contain a file named "stubs.zip" which includes stubs of the Java sources. When you build a cn1lib using a Codename One Library project, it will automatically generate stubs of the source so that the IDE will have access to nice things like Javadoc when using the library. The kotlin distribution includes a separate jar file with the runtime sources, named "kotlin-runtime-sources.jar", so you used this as the "stubs". It contains full sources, which isn't necessary, but it also doesn't hurt. +To make the library easier to use the cn1lib file can also contain a file named "stubs.zip" which includes stubs of the Java sources. When you build a cn1lib using a Codename One Library project, it will automatically generate stubs of the source so that the IDE will have access to nice things like Javadoc when using the library. The kotlin distribution includes a separate jar file with the runtime sources, named `kotlin-runtime-sources.jar`, so you used this as the "stubs." It contains full sources, which isn't necessary, but it also doesn't hurt. -So now that you had my two jar files: kotlin-runtime.jar and kotlin-runtime-sources.jar, I created a new empty directory, and copied them inside. I renamed the jars "main.zip" and "stubs.zip" respectively. Then I zipped up the directory and renamed the zip file "kotlin-runtime.cn1lib". +now that you had your two jar files: kotlin-runtime.jar and kotlin-runtime-sources.jar, I created a new empty directory, and copied them inside. I renamed the jars "main.zip" and "stubs.zip" respectively. Then I zipped up the directory and renamed the zip file `kotlin-runtime.cn1lib`. -IMPORTANT: Building cn1libs manually in this way is a ** bad habit, as it bypasses the API verification step that normally occurs when building a library project. it's possible, even likely, that the jar files that you convert depend on classes that aren't in the Codename One library, so your library will fail at runtime in unexpected ways. The reason you could do this with kotlin's runtime (with some confidence) is because I already analyzed the bytecodes to ensure that they didn't include anything problematic. +IMPORTANT: Building cn1libs manually in this way is a ** bad habit, as it bypasses the API verification step that occurs when building a library project. it's possible, even likely, that the jar files that you convert depend on classes that aren't in the Codename One library, so your library will fail at runtime in unexpected ways. The reason you could do this with kotlin's runtime (with some confidence) is because I already analyzed the bytecodes to ensure that they didn't include anything problematic. -===== Step 3: Hello World +===== Step 3: hello world -For your "Hello World" test you will need to create a separate project in your JVM language and produce class files that you will *manually* copy into an appropriate location of your project. You will want to use the *normal* tools for the language and not worry about how it integrates with Codename One. For Kotlin, I followed the getting started tutorial on the Kotlin site to create a new Kotlin project in IntelliJ. When Steve ported Mirah, he used a text editor and the mirahc command-line compiler to create my Hello World class. The tools and process will depend on the language. +For your "Hello World" test you will need to create a separate project in your JVM language and produce class files that you will *manually* copy into an appropriate location of your project. You will want to use the *normal* tools for the language and not worry about how it integrates with Codename One. For Kotlin, I followed the getting started tutorial on the Kotlin site to create a new Kotlin project in IntelliJ. When Steve ported Mirah, he used a text editor and the mirahc command-line compiler to create your Hello World class. The tools and process will depend on the language. Here is the "hello world" you created in Kotlin: @@ -3126,26 +3131,26 @@ After building this, I have a directory that contains "com/mycompany/myapp/Hello It also produced a.jar file that contains this class. -The easiest way to integrate external code into a Codename One project, is to wrap it as a cn1lib file and place it into my Codename One project's lib directory. That way you don't have to mess with any of the build files. So, using roughly the same procedure as you used to create the kotlin-runtime.cn1lib, I wrap my hellokotlin.jar as a cn1lib to produce "hellokotlin.cn1lib" and copy it to the "lib" directory of a Codename One project. +The easiest way to integrate external code into a Codename One project, is to wrap it as a cn1lib file and place it into your Codename One project's lib directory. That way you don't have to mess with any of the build files. using the same procedure as you used to create the kotlin-runtime.cn1lib, I wrap your hellokotlin.jar as a cn1lib to produce "hellokotlin.cn1lib" and copy it to the "lib" directory of a Codename One project. NOTE: Remember to select "Codename One" -> "Refresh CN1Libs" after placing the cn1lib in your lib directory or it won't get picked up. -Finally, I call my library from the start() method of my app: +Finally, I call your library from the start() method of your app: [source,java] ---- include::../demos/common/src/main/java/com/codename1/hellokotlin2/KotlinForm.java[tag=kotlinForm,indent=0] ---- -If you run this in the Simulator, it should print "Hello from Kotlin" in the output console. If you get an error, then you can dig in and try to figure out what went wrong using my standard debugging techniques. *EXPECT* an error on the first run. Hopefully it will be a missing import or something simple. +If you run this in the Simulator, it should print "Hello from Kotlin" in the output console. If you get an error, then you can dig in and try to figure out what went wrong using your standard debugging techniques. *EXPECT* an error on the first run. Hopefully it will be a missing import or something simple. -===== Step 4: A More Complex Hello World +===== Step 4: a more complex hello world -In the case of Kotlin, the hello world example app would actually run without the runtime library because it was so simple. So it was necessary to add a more complex example to prove the need for the runtime library. It doesn't matter what you do with your more complex example, as long as it doesn't require classes that aren't in Codename One. +For Kotlin, the hello world example app would actually run without the runtime library because it was so simple. it was necessary to add a more complex example to prove the need for the runtime library. It doesn't matter what you do with your more complex example, as long as it doesn't require classes that aren't in Codename One. If you want to use the Codename One inside your project, you should add the CodenameOne.jar (found inside any Codename One project) to your classpath so that it will compile. -===== Step 5: Automation and Integration +===== Step 5: automation and integration At this point you already have a manual process for incorporating files built with your alternate language into a Codename One project. The process looks like: @@ -3157,28 +3162,28 @@ At this point you already have a manual process for incorporating files built wi When Steve first developed Mirah support he automated this process using an https://github.com/shannah/CN1MirahNBM/blob/master/src/ca/weblite/codename1/mirah/build.xml[ANT script]. He also automatically generated some bootstrap code so that he could develop the whole app in Mirah and he woudn't have to write any Java. For example, this level of integration has limitations. -For example, with this approach alone, you couldn't have two-way dependencies between Java source and Mirah source. Yes, Mirah code could use Java libraries (and it did depend on CodenameOne.jar), and my Java code could use my Mirah code. For example, Mirah *source* code could not depend on the Java *source* code in my project. This has to do with the order in which code is compiled. it's a bit of a chicken and egg issue. If you're building a project that has Java source code and Mirah source code, you're using two different compilers: mirahc to compile the Mirah files, and javac to compile the Java files. If you're starting from a clean build, and you run mirahc first, then the.java files haven't yet been compiled to.class files - and thus mirahc can't *reference* them - and any mirah code that depends on those uncompiled Java classes will fail. If you compile the.java files first, then you've the opposite problem. +For example, with this approach alone, you couldn't have two-way dependencies between Java source and Mirah source. Yes, Mirah code could use Java libraries (and it did depend on CodenameOne.jar), and your Java code could use your Mirah code. For example, Mirah *source* code couldn't depend on the Java *source* code in your project. This has to do with the order in which code is compiled. it's a bit of a chicken and egg issue. If you're building a project that has Java source code and Mirah source code, you're using two different compilers: mirahc to compile the Mirah files, and javac to compile the Java files. If you're starting from a clean build, and you run mirahc first, then the.Java files haven't yet been compiled to.class files - and thus mirahc can't *reference* them - and any mirah code that depends on those uncompiled Java classes will fail. If you compile the.Java files first, then you've the opposite problem. -Steve worked around this problem in Mirah by writing https://github.com/shannah/mirah-ant/blob/master/src/ca/weblite/asm/JavaExtendedStubCompiler.java[my own pseudo-compiler] that produced stub class files for the java source that would be referenced by mirahc when compiling the Mirah files. In this way he was able to have two-way dependencies between Java and Mirah in the same project. +Steve worked around this problem in Mirah by writing https://github.com/shannah/mirah-ant/blob/master/src/ca/weblite/asm/JavaExtendedStubCompiler.java[your own pseudo-compiler] that produced stub class files for the java source that would be referenced by mirahc when compiling the Mirah files. In this way he was able to have two-way dependencies between Java and Mirah in the same project. Kotlin also supports two-way dependencies, probably using a similar mechanism. -====== How Seamless Can You Make It? +====== How seamless can you make it -For both the Kotlin and Mirah support, you wanted integration to be seamless. You didn't want users to have to create a separate project for their Kotlin/Mirah code. You wanted them to simply add a Kotlin/Mirah file into their project and have it * work*. Achieving this level of integration in Kotlin was easy, since they provide an https://kotlinlang.org/docs/reference/using-ant.html[ANT plugin] that essentially allowed me to add one tag inside my `` tags: +For both the Kotlin and Mirah support, you wanted integration to be seamless. You didn't want users to have to create a separate project for their Kotlin/Mirah code. You wanted them to add a Kotlin/Mirah file into their project and have it * work*. Achieving this level of integration in Kotlin was easy, since they provide an https://kotlinlang.org/docs/reference/using-ant.html[ANT plugin] that essentially allowed you to add one tag inside your `` tags: [source,xml] ---- ---- -And it would automatically handle Kotlin and Java files together: Seamlessly. There are a few places in a Codename One's build.xml file where you call "javac" so you needed to inject these tags in those places. This injection is performed automatically by the Codename One IntelliJ plugin. +And it would automatically handle Kotlin and Java files together: Seamlessly. a few places in a Codename One's build.xml file where you call "javac" so you needed to inject these tags in those places. This injection is performed automatically by the Codename One IntelliJ plugin. For Mirah, Steve developed his own https://github.com/shannah/mirah-ant[ANT plugins] and https://github.com/shannah/mirah-nbm[Netbeans module] that do something similar in Netbeans. -=== Update Framework +=== Update framework -When you launched Codename One in 2012 you needed a way to ship updates and fixes faster than the plugin update system. So you built the client lib update system. Then you needed a way to update the designer tool (resource editor), the GUI builder & the skins... You also needed a system to update the built-in builder code (`CodeNameOneBuildClient.jar` so you built a tool for that too). +When Codename One launched in 2012 there was a need to ship updates and fixes faster than the plugin update system, leading to the client lib update system. There was also a need to update the designer tool (resource editor), the GUI builder, and the skins, as well as the built-in builder code (`CodeNameOneBuildClient.jar`). The https://github.com/codenameone/UpdateCodenameOne[Update Framework] solves many problems in the old systems: @@ -3188,11 +3193,11 @@ The https://github.com/codenameone/UpdateCodenameOne[Update Framework] solves ma - Update of settings/designer without IDE plugin update - The IDE plugin update process is slow and tedious. This way you can push out a bug fix for the GUI builder without going through the process of releasing a new plugin version -For the most part this framework should be seamless. You should no longer see the "downloading" message whenever you push an update after your build client is updated. Your system would poll for a new version daily and update when new updates are available. +This framework should be seamless. You should no longer see the "downloading" message whenever you push an update after your build client is updated. Your system would poll for a new version daily and update when new updates are available. You can also use the usual method of #Codename One Settings# -> #Basic# -> #Update Client Libs# which will force an update check. Notice that the UI will look a bit different after this update. -==== How does it Work? +==== How does it work You can see the full code https://github.com/codenameone/UpdateCodenameOne[here] the gist of it's simple. You create a jar called `UpdateCodenameOne.jar` under `~/.codenameone` (`~` represents the users home directory). @@ -3234,7 +3239,7 @@ TIP: Exclude `Versions.properties` from Git Under the `~/.codenameone` directory you've a more detailed `UpdateStatus.properties` file that includes versions of the locally downloaded files. Notice you can delete this file and it will be recreated as all the jars get downloaded over again. -==== What isn't Covered +==== What isn't covered You will notice 3 big things that aren't covered in this unified framework: diff --git a/docs/developer-guide/Animations.asciidoc b/docs/developer-guide/Animations.asciidoc index 6cb4a1ca4a..b83f30bea8 100644 --- a/docs/developer-guide/Animations.asciidoc +++ b/docs/developer-guide/Animations.asciidoc @@ -1,13 +1,13 @@ == Animations -There are many ways to animate and liven the data within a Codename One application, layout animations are probably chief among them. But first you need to understand some basics such as layout reflows. +many ways to animate and liven the data within a Codename One application, layout animations are probably chief among them. But first you need to understand some basics such as layout reflows. [[layout-reflows]] -=== Layout Reflow +=== Layout reflow -Layout in tools such as HTML is implicit, when you add something into the UI it's automatically placed correctly. Other tools such as Codename One use explicit layout, that means you've to explicitly request the UI to layout itself after making changes! +Layout in tools such as HTML is implicit, when you add something into the UI it's automatically placed. Other tools such as Codename One use explicit layout, that means you've to explicitly request the UI to layout itself after making changes! -IMPORTANT: Like many such rules exceptions occur. E.g. if the device is rotated or window size changes a layout will occur automatically. +IMPORTANT: Like many such rules exceptions occur. For example, if the device is rotated or window size changes a layout will occur automatically. When adding a component to a UI that's already visible, the component won't show by default. @@ -15,23 +15,23 @@ TIP: When adding a component to a form which isn't shown on the screen, there is The chief advantage of explicit layout is performance. -E.g. imagine adding 100 components to a form. If the form was laid out automatically, layout would have happened 100 times instead of once when adding was finished. In fact layout reflows are often considered the #1 performance issue for HTML/JavaScript applications. +For example, imagine adding 100 components to a form. If the form was laid out automatically, layout would have happened 100 times instead of once when adding was finished. In fact layout reflows are often considered the #1 performance issue for HTML/JavaScript applications. -NOTE: Smart layout reflow logic can alleviate some of the pains of the automatic layout reflows however since the process is implicit it's almost impossible to optimize complex usages across browsers/devices. A major JavaScript performance tip is to use absolute positioning which is akin to not using layouts at all! +NOTE: Smart layout reflow logic can alleviate some pains of the automatic layout reflows but since the process is implicit it's almost impossible to optimize complex usages across browsers/devices. A major JavaScript performance tip is to use absolute positioning which is akin to not using layouts at all! that's why, when you add components to a form that's already showing, you should invoke `revalidate()` or animate the layout appropriately. This also enables the layout animation behavior explained below. [[layout-animations]] -=== Layout Animations -To understand animations you need to understand a couple of things about Codename One components. When we add a component to a container, it's generally added but not positioned anywhere. A novice might notice the `setX`/`setY`/`setWidth`/`setHeight` methods on a component and try to position it absolutely. +=== Layout animations +To understand animations you need to understand a couple of things about Codename One components. When you add a component to a container, it's added but not positioned anywhere. A novice might notice the `setX`/`setY`/`setWidth`/`setHeight` methods on a component and try to position it. This won't work since these methods are meant for the layout manager, which is implicitly invoked when a form is shown (internally in Codename One). The layout manager uses these methods to position/size the components based on the hints given to it. -If you add components to a form that's currently showing, it's your responsibility to invoke `revalidate`/`layoutContainer` to arrange the newly added components (see <>). +If you add components to a form that's showing, it's your responsibility to invoke `revalidate`/`layoutContainer` to arrange the newly added components (see <>). -`animateLayout()` method is a fancy form of revalidate that animates the components into their laid out position. After changing the layout & invoking this method the components move to their new sizes/positions seamlessly. `Form` exposes convenience wrappers such as `animateLayout*()` that simply forward to the underlying content pane, so you can usually call the methods directly on the form unless you specifically need to animate a nested container. +`animateLayout()` method is a fancy form of revalidate that animates the components into their laid out position. After changing the layout & invoking this method the components move to their new sizes/positions seamlessly. `Form` exposes convenience wrappers such as `animateLayout*()` that forward to the underlying content pane, so you can call the methods directly on the form unless you specifically need to animate a nested container. -This sort of behavior creates a special case where setting the size/position makes sense. When we set the size/position in the demo code here you're positioning the components at the animation start position above the frame. +This sort of behavior creates a special case where setting the size/position makes sense. When you set the size/position in the demo code here you're positioning the components at the animation start position above the frame. [source,java] ---- @@ -40,8 +40,8 @@ include::../demos/common/src/main/java/com/codenameone/developerguide/animations There are a couple of things that you should notice about this example: -<1> We used a button to do the animation rather than doing it on show. Since `show()` implicitly lays out the components it wouldn't have worked correctly. -<2> We used `hi.animateLayout(20000);`, which delegates to the `Form` content pane. If you need to animate a specific container (e.g. a nested layout), call `animateLayout()` on that container instead. +<1> You used a button to do the animation rather than doing it on show. Since `show()` implicitly lays out the components it wouldn't have worked. +<2> You used `hi.animateLayout(20000);`, which delegates to the `Form` content pane. If you need to animate a specific container (for example, a nested layout), call `animateLayout()` on that container instead. This results in: @@ -55,13 +55,13 @@ image:img/layout-animation-6.png[Frame 6] image:img/layout-animation-7.png[Frame 7] -==== Unlayout Animations +==== Unlayout animations -While layout animations are powerful effects for adding elements into the UI and drawing attention to them. The inverse of removing an element from the UI is often more important. E.g. when we delete or remove an element we want to animate it out. +While layout animations are powerful effects for adding elements into the UI and drawing attention to them. The inverse of removing an element from the UI is often more important. For example, when you delete or remove an element you want to animate it out. -Layout animations don't do that since they will try to bring the animated item into place. What we want is the exact opposite of a layout animation and that's the "unlayout animation". `Container.animateUnlayout(int, int, Runnable)` and the `Form.animateUnlayout*()` helpers let you trigger this transition either asynchronously (with a completion callback) or synchronously via the `AndWait` variants. +Layout animations don't do that since they will try to bring the animated item into place. What you want is the exact opposite of a layout animation and that's the "unlayout animation." `Container.animateUnlayout(int, int, Runnable)` and the `Form.animateUnlayout*()` helpers let's trigger this transition either asynchronously (with a completion callback) or synchronously via the `AndWait` variants. -The "unlayout animation" takes a valid laid out state and shifts the components to an invalid state that we defined in advance. E.g. you can fix the example above to flip the "fall" button into a "rise" button when the buttons come into place and this will allow the buttons to float back up to where they came from in the exact reverse order. +The "unlayout animation" takes a valid laid out state and shifts the components to an invalid state that you defined in advance. For example, you can fix the example above to flip the "fall" button into a "rise" button when the buttons come into place and this will allow the buttons to float back up to where they came from in the exact reverse order. WARNING: An unlayout animation always leaves the container in an invalidated state. @@ -73,18 +73,18 @@ include::../demos/common/src/main/java/com/codenameone/developerguide/animations You will notice some similarities with the unlayout animation but the differences represent the exact opposite of the layout animation: -<1> We loop over existing components (not newly created ones) -<2> We set the desired end position not the desired starting position -<3> We used the `animateUnlayoutAndWait(...)` variant to block until completion; `animateUnlayout(duration, opacity, callback)` provides the non-blocking alternative when you want to continue immediately and run code from a callback instead. +<1> You loop over existing components (not newly created ones) +<2> You set the desired end position not the desired starting position +<3> You used the `animateUnlayoutAndWait(...)` variant to block until completion; `animateUnlayout(duration, opacity, callback)` provides the non-blocking alternative when you want to continue and run code from a callback instead. <4> After the animation completes you need to actually remove the elements since the UI is now in an invalid position with elements outside of the screen but still physically there! -==== Hiding & Visibility +==== Hiding & visibility -A common trick for animating Components in Codename One is to set their preferred size to `0` and then invoke `animateLayout()` thus triggering an animation to hide said `Component`. There are several issues with this trick but one of the biggest ones is the fact that `setPreferredSize` has been deprecated for quite a while. +A common trick for animating Components in Codename One is to set their preferred size to `0` and then invoke `animateLayout()` thus triggering an animation to hide said `Component`. several issues with this trick but one of the biggest ones is the fact that `setPreferredSize` has been deprecated for quite a while. Instead of using that trick you can use `setHidden`/`isHidden` who effectively encapsulate this functionality and a bit more. -One of the issues `setHidden` tries to solve is the fact that preferred size doesn't include the margin in the total and thus a component might still occupy space despite being hidden. When you request the margin adjustment the current margins are cached, the component is given zero margins while hidden, and those cached values are restored when it's shown again—without resetting the UIID or other style state. +One of the issues `setHidden` tries to solve is the fact that preferred size doesn't include the margin in the total and thus a component might still occupy space despite being hidden. When you request the margin change the current margins are cached, the component is given zero margins while hidden, and those cached values are restored when it's shown again—without resetting the UIID or other style state. This functionality might be undesirable which is why there is a version of the `setHidden` method that accepts a boolean flag indicating whether the margin cache should be manipulated. You can effectively `hide`/`show` a component without deprecated code using something like this: @@ -95,17 +95,17 @@ include::../demos/common/src/main/java/com/codenameone/developerguide/animations TIP: Notice that the code above uses `setVisible()`, which shouldn't be confused with `setHidden`. `setVisible()` toggles the visibility of the component it would still occupy the same amount of space -==== Synchronicity In Animations +==== Synchronicity in animations Most animations have two or three variants: -- Standard animation e.g. `animateLayout(int)` or the non-blocking `animateUnlayout(int, int, Runnable)` -- And wait variant e.g. `animateLayoutAndWait(int)` / `animateUnlayoutAndWait(int, int)` -- Callback variant e.g. `animateLayoutFade(int, int, Runnable)` +- Standard animation for example, `animateLayout(int)` or the non-blocking `animateUnlayout(int, int, Runnable)` +- And wait variant for example, `animateLayoutAndWait(int)` / `animateUnlayoutAndWait(int, int)` +- Callback variant for example, `animateLayoutFade(int, int, Runnable)` -The standard animation is invoked when we don't care about the completion of the animation. You can do this for a standard animation. +The standard animation is invoked when you don't care about the completion of the animation. You can do this for a standard animation. -NOTE: Unlayout animations always leave the container in an invalid state, so even the standard helper expects you to tidy up either in the callback or immediately after the animation finishes. +NOTE: Unlayout animations always leave the container in an invalid state, so even the standard helper expects you to tidy up either in the callback or after the animation finishes. The `AndWait` variant blocks the calling thread until the animation completes. This is useful for sequencing animations one after the other e.g this code from the kitchen sink demo: @@ -117,7 +117,7 @@ include::../demos/common/src/main/java/com/codenameone/developerguide/animations First the UI goes thru an "unlayout" animation, once that completes the layout itself is performed. // HTML_ONLY_START -IMPORTANT: The `AndWait` calls needs to be invoked on the Event Dispatch Thread despite being "blocking". This is a common convention in Codename One powered by a unique capability of Codename One: `invokeAndBlock`. + +IMPORTANT: The `AndWait` calls needs to be invoked on the Event Dispatch Thread despite being "blocking." This is a common convention in Codename One powered by a unique capability of Codename One: `invokeAndBlock`. + You can learn more about `invokeAndBlock` in the https://www.codenameone.com/manual/edt.html[EDT section]. // HTML_ONLY_END @@ -128,16 +128,16 @@ You can learn more about `invokeAndBlock` in the <>. //// -The callback variant is similar to the `invokeAndBlock` variant but uses a more conventional callback semantic which is more familiar to some developers. It accepts a `Runnable` callback that will be invoked after the fact. E.g. you can change the <> to use the callback semantics as such: +The callback variant is like the `invokeAndBlock` variant but uses a more conventional callback semantic which is more familiar to some developers. It accepts a `Runnable` callback that will be invoked after the fact. For example, you can change the <> to use the callback semantics as such: [source,java] ---- include::../demos/common/src/main/java/com/codenameone/developerguide/animations/UnlayoutAnimationsDemo.java[tag=unlayoutCallback,indent=0] ---- -===== Animation Fade and Hierarchy +===== Animation fade and hierarchy -There are several additional variations on the standard animate methods. Several methods accept a numeric `fade` argument. This is useful to fade out an element in an "unlayout" operation or fade in a regular animation. +several more variations on the standard animate methods. Several methods accept a numeric `fade` argument. This is useful to fade out an element in an "unlayout" operation or fade in a regular animation. The value for the fade argument is a number between 0 and 255 where 0 represents full transparency and 255 represents full opacity. @@ -150,12 +150,12 @@ include::../demos/common/src/main/java/com/codenameone/developerguide/animations The `demoComponents` `Vector` contains components from separate containers and this code would not work with a simple animate layout. -WARNING: We normally recommend avoiding the hierarchy version. Its slower but more importantly, it's flaky. Since the size/position of the `Container` might be affected by the layout the animation could get clipped and skip. These are hard issues to debug. +WARNING: Avoid the hierarchy version where possible. Its slower but more importantly, it's flaky. Since the size/position of the `Container` might be affected by the layout the animation could get clipped and skip. These are hard issues to debug. -==== Sequencing Animations Via AnimationManager +==== Sequencing animations via AnimationManager All the animations go thru a per-form queue: the https://www.codenameone.com/javadoc/com/codename1/ui/AnimationManager.html[AnimationManager]. -This effectively prevents two animations from mutating the UI in parallel so we won't have collisions between two conflicting sides. Things get more interesting when we try to do something like this: +This effectively prevents two animations from mutating the UI in parallel so you won't have collisions between two conflicting sides. Things get more interesting when you try to do something like this: [source,java] ---- @@ -171,13 +171,13 @@ The simple *yet problematic* fix would be: include::../demos/common/src/main/java/com/codenameone/developerguide/animations/AnimationManagerDemo.java[tag=animationManagerWait,indent=0] ---- -So why that might still fail? +why that might still fail? Events come in constantly during the run of the EDT footnote:[Event Dispatch Thread], so an event might come in that might trigger an animation in your code. Even if you're on the EDT keep in mind that you don't actually block it and an event might come in. In those cases an animation might start and you might be unaware of that animation and it might still be in action when you expect remove to work. -===== Animation Manager to the Rescue +===== Animation manager to the rescue https://www.codenameone.com/javadoc/com/codename1/ui/AnimationManager.html[AnimationManager] has built-in support to fix this exact issue. @@ -188,13 +188,13 @@ You can flush the animation queue and run synchronously after all the animations include::../demos/common/src/main/java/com/codenameone/developerguide/animations/AnimationManagerDemo.java[tag=animationManagerFlush,indent=0] ---- -=== low-level Animations +=== Low-level animations The Codename One event dispatch thread has a special animation "pulse" allowing an animation to update its state and draw itself. Code can make use of this pulse to implement repetitive polling tasks that have little to do with drawing. This is helpful since the callback will always occur on the event dispatch thread. -Every component in Codename One contains an `animate()` method that returns a boolean value, you can also implement the https://www.codenameone.com/javadoc/com/codename1/ui/animations/Animation.html[Animation] interface in an arbitrary component to implement your own animation. In order to receive animation events you need to register yourself within the parent form, it's the responsibility of the parent for to call `animate()`. +Every component in Codename One contains an `animate()` method that returns a boolean value, you can also implement the https://www.codenameone.com/javadoc/com/codename1/ui/animations/Animation.html[Animation] interface in an arbitrary component to implement your own animation. To receive animation events you need to register yourself within the parent form, it's the responsibility of the parent for to call `animate()`. If the `animate` method returns true then the animation will be painted (the `paint` method of the `Animation` interface would be invoked). @@ -202,7 +202,7 @@ TIP: it's important to deregister animations when they aren’t needed to conser If you derive from a component, which has its own animation logic you might damage its animation behavior by deregistering it, so tread gently with the low-level API’s. -E.g. you can add additional animation logic using code like this: +For example, you can add more animation logic using code like this: [source,java] ---- @@ -214,41 +214,41 @@ include::../demos/common/src/main/java/com/codenameone/developerguide/animations include::../demos/common/src/main/java/com/codenameone/developerguide/animations/LowLevelAnimationDemo.java[tag=lowLevelAnimate,indent=0] ---- -==== Why Not Just Write Code In Paint? +==== Why not just write code in paint Animations are comprised of two parts, the logic (deciding the position etc) and the painting. The paint method should be dedicated to painting, not to the actual moving of the components. -The separation of concerns allows us to avoid redundant painting e.g. if animate didn't trigger a change return `false` to avoid the overhead related to animations. +The separation of concerns allows you to avoid redundant painting for example, if animate didn't trigger a change return `false` to avoid the overhead related to animations. // HTML_ONLY_START -We discuss low-level animations in more details within the https://www.codenameone.com/manual/graphics.html#clock-animation-section[animation section of the clock demo]. +You discuss low-level animations in more details within the https://www.codenameone.com/manual/graphics.html#clock-animation-section[animation section of the clock demo]. // HTML_ONLY_END //// //PDF_ONLY -We discuss low-level animations in more details within the <>. +You discuss low-level animations in more details within the <>. //// === Transitions -Transitions allow us to replace one component with another, most typically forms or dialogs are replaced with a transition however a transition can be applied to replace any arbitrary component. +Transitions allow you to replace one component with another, most typically forms or dialogs are replaced with a transition but a transition can be applied to replace any arbitrary component. Developers can implement their own custom transition and install it to components by deriving the https://www.codenameone.com/javadoc/com/codename1/ui/animations/Transition.html[Transition] class, although most commonly the built in https://www.codenameone.com/javadoc/com/codename1/ui/animations/CommonTransitions.html[CommonTransitions] class is used for almost everything. You can define transitions for forms/dialogs/menus globally either via the theme constants or via the -https://www.codenameone.com/javadoc/com/codename1/ui/plaf/LookAndFeel.html[LookAndFeel] class. Alternatively you can install a transition on top-level components via setter methods. +https://www.codenameone.com/javadoc/com/codename1/ui/plaf/LookAndFeel.html[LookAndFeel] class. Or you can install a transition on top-level components via setter methods. .In/Out Transitions **** -When defining a transition we define the entering transition and the exiting transition. For most cases only one of those is necessary and we default to the exiting (out transition) as a convention. +When defining a transition you define the entering transition and the exiting transition. For most cases only one of those is necessary and you default to the exiting (out transition) as a convention. -So for almost all cases the method `setFormTransitonIn` should go unused. That API exists for some elaborate custom transitions that might need to have a special effect both when transitioning in and out of a specific form. However, most of these effects are easier to achieve with layout animations (e.g. components dropping into place etc.). +for most cases the method `setFormTransitonIn` should go unused. That API exists for some elaborate custom transitions that might need to have a special effect both when transitioning in and out of a specific form. But, most of these effects are easier to achieve with layout animations (for example, components dropping into place etc.). -In the case of `Dialog` the transition in shows its appearance and the transition out shows its disposal. So in that case both transitions make a lot of sense. +For `Dialog` the transition in shows its appearance and the transition out shows its disposal. in that case both transitions make a lot of sense. **** .Back/Forward Transitions **** -Transitions have a direction and can all be played either in incoming or outgoing direction. A transition can be flipped (played in reverse) when use an RTL language footnote:[Right to left/bidi language such as Hebrew or Arabic] or when we simply traverse backwards in the form navigation hierarchy. +Transitions have a direction and can all be played either in incoming or outgoing direction. A transition can be flipped (played in reverse) when use an RTL language footnote:[Right to left/bidi language such as Hebrew or Arabic] or when you traverse backwards in the form navigation hierarchy. Normally `Form.show()` displays the next `Form` with an incoming transition based on the current RTL mode. If use `Form.showBack()` it will play the transition in reverse. **** @@ -257,7 +257,7 @@ TIP: When working with high-level animations you can select #Slow Motion# option Themes define the default transitions used when showing a form, these differ based on the OS. In most platforms the default is `Slide` whereas in iOS the default is `SlideFade` which slides the content pane and title while fading in/out the content of the title area. -TIP: `SlideFade` is problematic without a title area. If you've a `Form` that lacks a title area we would recommend to disable `SlideFade` at least for that `Form`. +TIP: `SlideFade` is problematic without a title area. If you've a `Form` that lacks a title area you would recommend to disable `SlideFade` at least for that `Form`. // HTML_ONLY_START Check out the full set of theme constants in the https://www.codenameone.com/manual/advanced-theming.html#theme-constants-section[Theme Constants Section]. @@ -276,18 +276,18 @@ To apply a transition to a component you can use the `Container.replace()` metho include::../demos/common/src/main/java/com/codenameone/developerguide/animations/ReplaceTransitionDemo.java[tag=replaceTransition,indent=0] ---- -TIP: Replace even works when you've a layout constraint in place e.g. replacing a component in a border layout will do the "right thing". However, some layouts such as `TableLayout` might be tricky in such cases so recommend wrapping a potentially replaceable `Component` in a border layout and replacing the content. +TIP: Replace even works when you've a layout constraint in place for example, replacing a component in a border layout will do the "right thing." But, some layouts such as `TableLayout` might be tricky in such cases so recommend wrapping a replaceable `Component` in a border layout and replacing the content. `Container.replace()` can also be used with a null transition at which point it replaces instantly with no transition. -==== Slide Transitions +==== Slide transitions The slide transitions are used to move the `Form/Component` in a sliding motion to the side or up/down. There are 4 basic types of slide transitions: . Slide - the most commonly used transition . Fast Slide - historically this provided better performance for old device types. it's no longer recommended for newer devices . Slide Fade - the iOS default where the title area features a fade transition -. Cover/Uncover - a type of slide transition where the source or destination form slides while the other remains static in place +. Cover/Uncover - a kind of slide transition where the source or destination form slides while the other remains static in place The code below demonstrates the usage of all the main transitions: @@ -306,7 +306,7 @@ image::img/transition-slide-vertical.jpg[The slide transition can be applied ver .Slide fade fades in the destination title while sliding the content pane it's the default on iOS image::img/transition-slide-fade.jpg[Slide fade fades in the destination title while sliding the content pane its the default on iOS,scaledwidth=70%] -TIP: `SlideFade` is problematic without a title area. If you've a `Form` that lacks a title area we would recommend to disable `SlideFade` at least for that `Form`. +TIP: `SlideFade` is problematic without a title area. If you've a `Form` that lacks a title area you would recommend to disable `SlideFade` at least for that `Form`. .With cover transitions the source form stays in place as it's covered by the destination. This transition can be played both horizontally and vertically image::img/transition-cover.jpg[With cover transitions the source form stays in place as it's covered by the destination. This transition can be played both horizontally and vertically,scaledwidth=70%] @@ -315,7 +315,7 @@ image::img/transition-cover.jpg[With cover transitions the source form stays in .Uncover is the inverse of cover. The destination form stays in place while the departing form moves away image::img/transition-uncover.jpg[Uncover is the inverse of cover. The destination form stays in place while the departing form moves away,scaledwidth=70%] -==== Fade and Flip Transitions +==== Fade and flip transitions The fade transition is pretty trivial and accepts a time value since it has no directional context. @@ -329,14 +329,14 @@ IMPORTANT: This transition looks different on devices as it uses native perspect .Fade transition is probably the simplest one around image::img/transition-flip.jpg[Fade transition is probably the simplest one around,scaledwidth=70%] -==== Bubble Transition +==== Bubble transition https://www.codenameone.com/javadoc/com/codename1/ui/animations/BubbleTransition.html[BubbleTransiton] morphs a component into another component using a circular growth motion. The `BubbleTransition` accepts the component that will grow into the bubble effect as one of its arguments. it's generally designed for `Dialog` transitions although it could work for more creative use cases: -NOTE: The code below manipulates styles and look. This is done to make the code more "self contained". Real world code should probably use the theme +NOTE: The code below manipulates styles and look. This is done to make the code more "self contained." Real world code should probably use the theme [source,java] ---- @@ -350,7 +350,7 @@ image::img/transition-bubble.png[Bubble transition converting a circular button // TODO: I need to rewrite this section... -==== Morph Transitions +==== Morph transitions Android's material design has a morphing effect where an element from the previous form (activity) animates into a different component on a new activity. Codename One has a morph effect in the https://www.codenameone.com/javadoc/com/codename1/ui/Container.html[Container] class but it @@ -365,7 +365,7 @@ To support this behavior you've the https://www.codenameone.com/javadoc/com/code the rest of the UI (see <>). Since the transition is created before the form exists you can't reference explicit components within the form -when creating the morph transition (in order to indicate which component becomes which) so you need to refer +when creating the morph transition (to show which component becomes which) so you need to refer to them by name. This means you need to use `setName(String)` on the components in the source/destination forms so the transition will be able to find them. @@ -383,7 +383,7 @@ iOS7+ allows swiping back one form to the previous form, Codenmae One has an API include::../demos/common/src/main/java/com/codenameone/developerguide/animations/SwipeBackSupportDemo.java[tag=swipeBackBind,indent=0] ---- -That one command will enable swiping back from `currentForm`. https://www.codenameone.com/javadoc/com/codename1/util/LazyValue.html[LazyValue] allows us to pass a value lazily: +That one command will enable swiping back from `currentForm`. https://www.codenameone.com/javadoc/com/codename1/util/LazyValue.html[LazyValue] allows you to pass a value lazily: [source,java] ---- @@ -402,7 +402,7 @@ public interface LazyValue { } ---- -This effectively allows us to pass a form and create it as necessary (e.g. for a GUI builder app we don't +This effectively allows you to pass a form and create it as necessary (for example, for a GUI builder app you don't have the actual previous form instance), notice that the arguments aren't used for this case but will be used in other cases. diff --git a/docs/developer-guide/Casual-Game-Programming.asciidoc b/docs/developer-guide/Casual-Game-Programming.asciidoc index 9a3df0ac02..8aaf675546 100644 --- a/docs/developer-guide/Casual-Game-Programming.asciidoc +++ b/docs/developer-guide/Casual-Game-Programming.asciidoc @@ -4,47 +4,47 @@ Casual games are often the most influential games of all, they cross demographic Yes, framerates are important but ubiquity, social connectivity & gameplay are even more important for this sub genre of the game industry. The mobile aspect highlights this point further, the way app stores are built releasing often puts your game at an advantage over its competitor’s. Yet releasing to all platforms and all screen sizes becomes an issue soon enough. -Typically a game is comprised of a game loop which updates UI status based on game time and renders the UI. However, with casual games constantly rendering is redundant and with mobile games it could put a major drain on the battery life. Instead you will use components to build the game elements and let Codename One do the rendering for us. +Typically a game is comprised of a game loop which updates UI status based on game time and renders the UI. But, with casual games constantly rendering is redundant and with mobile games it could put a major drain on the battery life. Instead you will use components to build the game elements and let Codename One do the rendering for us. -=== The Game +=== The game -You will create a poker game for 1 player that doesn't include the betting process or any of the complexities such as AI, card evaluation or validation. This allows us to fit the whole source code in 270 lines of code (more due to comments). I also chose to simplify the UI for touch devices, technically it would be pretty easy to add keypad support but it would complicate the code and require additional designs (for focus states). +You will create a poker game for 1 player that doesn't include the betting process or any of the complexities such as AI, card evaluation or validation. This allows you to fit the whole source code in 270 lines of code (more due to comments). I also chose to simplify the UI for touch devices, technically it would be pretty easy to add keypad support but it would complicate the code and require more designs (for focus states). TIP: You can see the game running on the simulator at http://www.youtube.com/watch?v=4IQGBT3VsSQ[http://www.youtube.com/watch?v=4IQGBT3VsSQ] The game consists of two forms: Splash screen and the main game UI. -==== Handling Multiple Device Resolutions +==== Handling multiple device resolutions -In mobile device programming every pixel is crucial because of the small size of the screen, however you can’t shrink down our graphics too much because it needs to be "finger friendly" (big enough for a finger) and readable. There is great disparity in the device world, even within the iOS family the retina iPad has more than twice the screen density of the iPad mini. This means that an image that looks good on the iPad mini will seem either small or pixelated on an iPad, on the other hand an image that looks good on the iPad would look huge (and take up too much RAM) on the iPad mini. The situation is even worse when dealing with phones and Android devices. +In mobile device programming every pixel is crucial because of the small size of the screen, but you can’t shrink down your graphics too much because it needs to be "finger friendly" (big enough for a finger) and readable. There is great disparity in the device world, even within the iOS family the retina iPad has more than twice the screen density of the iPad mini. This means that an image that looks good on the iPad mini will seem either small or pixelated on an iPad, but an image that looks good on the iPad would look huge (and take up too much RAM) on the iPad mini. The situation is even worse when dealing with phones and Android devices. -Thankfully there are solutions, such as using multiple images for every density (DPI). However, this is tedious for developers who need to scale the image and copy it every time for every resolution. Codename One has a feature called `MultiImage` which implicitly scales the images to all the resolutions on the desktop and places them within the res file, in runtime you will get the image that matches your devices density. +Thankfully there are solutions, such as using multiple images for every density (DPI). But, this is tedious for developers who need to scale the image and copy it every time for every resolution. Codename One has a feature called `MultiImage` which implicitly scales the images to all the resolutions on the desktop and places them within the res file, in runtime you will get the image that matches your devices density. -There is a catch though... `MultiImage` is designed for applications where we want the density to determine the size. So an iPad will have the same density as an iPhone since both share the same amount of pixels per inch. This makes sense for an app since the images will be big enough to touch and clear. Furthermore, since the iPad screen is larger more data will fit on the screen! +There is a catch though... `MultiImage` is designed for applications where you want the density to determine the size. an iPad will have the same density as an iPhone since both share the same amount of pixels per inch. This makes sense for an app since the images will be big enough to touch and clear. Furthermore, since the iPad screen is larger more data will fit on the screen! -However, game developers have a different constraint when it comes to game elements. In the case of a game we want the images to match the device resolution and take up as much screen real estate as possible, otherwise our game would be constrained to a small portion of the tablet and look small. There is a solution though, you can determine our own DPI level when loading resources and effectively force a DPI based on screen resolution when working with game images! +But, game developers have a different constraint when it comes to game elements. For a game you want the images to match the device resolution and take up as much screen real estate as possible, otherwise your game would be constrained to a small portion of the tablet and look small. There is a solution though, you can determine your own DPI level when loading resources and effectively force a DPI based on screen resolution when working with game images! -To work with such varied resolutions/DPI’s and potential screen orientation changes you need another tool in our arsenal: layout managers. +To work with such varied resolutions/DPI’s and potential screen orientation changes you need another tool in your arsenal: layout managers. -If you're familiar with AWT/Swing this should be pretty easy, Codename One allows you to codify the logic that flows Components within the UI. You will use the layout managers to facilitate that logic and preserve the UI flow when the device is rotated. +If you're familiar with AWT/Swing this should be pretty easy, Codename One allows you to codify the logic that flows Components within the UI. You will use the layout managers to ease that logic and preserve the UI flow when the device is rotated. ==== Resources -To save some time/effort we suggest using the ready made resource files linked in the On The Web section below. I suggest skipping this section and moving on to the code, however for completeness here is what we did to create these resources: +To save some time/effort use the ready-made resource files linked in the On The Web section below. You can skip this section and move on to the code, but for completeness here is what was done to create these resources: You will need a gamedata.res file that contains all the 52 cards as multi images using the naming convention of ‘rank suite.png’ example: 10c.png (10 of clubs) or ad.png (Ace of diamonds). -To accomplish this you can create 52 images of roughly 153x217 pixels for all the cards then use the designer tool and select "Quick Add MultiImages" from the menu. When prompted select HD resolution. This effectively created 52 multi-images for all relevant resolutions. +To do this you can create 52 images of 153×217 pixels for all the cards then use the designer tool and select "Quick Add MultiImages" from the menu. When prompted select HD resolution. This effectively created 52 multi-images for all relevant resolutions. -You can also modify the default theme that ships with the application in small ways to create the white over green color scheme. Open it in the designer tool by double clicking it and select the theme. +You can also change the default theme that ships with the application in small ways to create the white over green color scheme. Open it in the designer tool by double clicking it and select the theme. Then press Add and select the `Form` entry with background `NONE`, background color `6600` and transparency `255`. Add a `Label` style with transparency `0` and foreground `255` and then copy the style to pressed/selected (since its applied to buttons too). -Do the same for the `SplashTitle`/`SplashSubtitle` but there also set the alignment to `CENTER`, the `Font` to bold and in the case of `SplashTitle` to `Large Font` as well. +Do the same for the `SplashTitle`/`SplashSubtitle` but there also set the alignment to `CENTER`, the `Font` to bold and for `SplashTitle` to `Large Font` as well. -==== The Splash Screen +==== The splash screen The first step is creating the splash animation as you can see in the screenshots in <>. @@ -52,34 +52,34 @@ The first step is creating the splash animation as you can see in the screenshot .Animation stages for the splash screen opening animation image::img/gaming-fig2.png[Figure 2,scaledwidth=40%] -The animation in the splash screen and most of the following animations are achieved using the simple tool of layout animations. In Codename One components are automatically arranged into position using layout managers, however this isn't implicit unless the device is rotated. A layout animation relies on this fact, it allows you to place components in a position (whether by using a layout manager or by using `setX`/`setY`) then invoke the layout animation code so they will slide into their "proper" position based on the layout manager rules. +The animation in the splash screen and most of the following animations are achieved using the simple tool of layout animations. In Codename One components are automatically arranged into position using layout managers, but this isn't implicit unless the device is rotated. A layout animation relies on this fact, it allows you to place components in a position (whether by using a layout manager or by using `setX`/`setY`) then invoke the layout animation code so they will slide into their "proper" position based on the layout manager rules. -You can see how we achieved the splash screen animation of the cards sliding into place in Listing 1 within the `showSplashScreen()` method. After we change the layout to a box X layout we invoke animateHierarchy to animate the cards into place. +You can see how you achieved the splash screen animation of the cards sliding into place in Listing 1 within the `showSplashScreen()` method. After you change the layout to a box X layout you invoke animateHierarchy to animate the cards into place. -Notice that you use the `callSerially` method to start the actual animation. This call might not seem necessary at first until you try running the code on iOS. The first screen of the UI is important for the iOS port which uses a screenshot to speed startup. If we won’t have this callSerially invocation the screenshot rendering process won't succeed and the animation will stutter. +Notice that you use the `callSerially` method to start the actual animation. This call might not seem necessary at first until you try running the code on iOS. The first screen of the UI is important for the iOS port which uses a screenshot to speed startup. If you won’t have this callSerially invocation the screenshot rendering process won't succeed and the animation will stutter. -We also have a cover transition defined here; it’s a simple overlay when moving from one form to another. +You also have a cover transition defined here; it’s a simple overlay when moving from one form to another. -==== The Game UI +==== The game UI -Initially when entering the game form you've another animation where all the cards are laid out as you can see in <>. We then have a long sequence of animation where the cards unify into place to form a pile (with a cover background falling on top) after which dealing begins and cards animate to the rival (with back showing) or to you with the face showing. Then the instructions to swap cards fade into place. +Initially when entering the game form you've another animation where all the cards are laid out as you can see in <>. You then have a long sequence of animation where the cards unify into place to form a pile (with a cover background falling on top) after which dealing begins and cards animate to the rival (with back showing) or to you with the face showing. Then the instructions to swap cards fade into place. [[game-figure-3]] .Game form startup animation and deal animation image::img/game-figure-3.png[Game UI,scaledwidth=40%] -This animation is easy to accomplish although it does have several stages. In the first stage we layout the cards within a grid layout (13x4), then when the animation starts (see the https://www.codenameone.com/javadoc/com/codename1/ui/util/UITimer.html[UITimer] code within `showGameUI()`) we change the layout to a layered layout, add the back card (so it will come out on top based on z-ordering) and invoke animate layout. +This animation is easy to do although it does have several stages. In the first stage you layout the cards within a grid layout (13×4), then when the animation starts (see the https://www.codenameone.com/javadoc/com/codename1/ui/util/UITimer.html[UITimer] code within `showGameUI()`) you change the layout to a layered layout, add the back card (so it will come out on top based on z-ordering) and invoke animate layout. -Notice that here you use `animateLayoutAndWait`, which effectively blocks the calling thread until the animation is completed. This is a VERY important and tricky subject! +Notice that here you use `animateLayoutAndWait`, which effectively blocks the calling thread until the animation is completed. This is a important and tricky subject! -Codename One is for the most part a single threaded API, it supports working on other threads but it's your responsibility to invoke everything on the EDT (Event Dispatch Thread). Since the EDT does the entire rendering, events etc. if you block it you will effectively stop Codename One in its place! However, there is a trick: invokeAndBlock is a feature that allows you to stop the EDT and do stuff then restore the EDT without "" stopping it. Its tricky, I won’t get into this in this article (this subject deserves an article of its own) but the gist of it's that you can’t invoke Thread.sleep() in a Codename One application (at least not on the EDT) but you can use clever methods such as `Dialog.show()`, `animateLayoutAndWait` etc. and they will block the EDT for you. This is convenient since you can write code serially without requiring event handling for every single feature. +Codename One is a single threaded API, it supports working on other threads but it's your responsibility to invoke everything on the EDT (Event Dispatch Thread). Since the EDT does the entire rendering, events etc. if you block it you will effectively stop Codename One in its place! But, there is a trick: invokeAndBlock is a feature that allows you to stop the EDT and do stuff then restore the EDT without "" stopping it. Its tricky, I won’t get into this in this article (this subject deserves an article of its own) but the gist of it's that you can’t invoke Thread.sleep() in a Codename One application (at least not on the EDT) but you can use clever methods such as `Dialog.show()`, `animateLayoutAndWait` etc. and they will block the EDT for you. This is convenient since you can write code serially without requiring event handling for every single feature. -Now that we got that out of the way, the rest of the code is clearer. Now we understand that `animateLayoutAndWait` will literally wait for the animation to complete and the next lines can do the next animation. Indeed after that we invoke the `dealCard` method that hands the cards to the players. This method is also blocking (using and `wait` methods internally) it also marks the cards as draggable and adds that drag and drop logic which you will later use to swap cards. +Now that you got that out of the way, the rest of the code is clearer. Now you understand that `animateLayoutAndWait` will wait for the animation to complete and the next lines can do the next animation. Indeed after that you invoke the `dealCard` method that hands the cards to the players. This method is also blocking (using and `wait` methods internally) it also marks the cards as draggable and adds that drag logic which you will later use to swap cards. -Last but not least in the animation department, you use a method called replace to fade in a component using a transition. +In the animation department, you use a method called replace to fade in a component using a transition. -To handle the dealing we added an action listener to the deck button, this action listener is invoked when the cards are dealt and that completes the game. +To handle the dealing an action listener is added to the deck button, this action listener is invoked when the cards are dealt and that completes the game. [source,java] @@ -240,7 +240,7 @@ public class Poker { // The game itself is comprised of 3 containers, one for each player containing a grid of 5 cards (grid layout // divides space evenly) and the deck of cards/dealer. Initially we show an animation where all the cards - // gather into the deck, that is why we set the initial deck layout to show the whole deck 4x13 + // gather into the deck, that is why we set the initial deck layout to show the whole deck 4×13 final Container deckContainer = new Container(new GridLayout(4, 13)); final Container playerContainer = new Container(new GridLayout(1, 5)); final Container rivalContainer = new Container(new GridLayout(1, 5)); diff --git a/docs/developer-guide/Events.asciidoc b/docs/developer-guide/Events.asciidoc index 24065ada91..24d4cb9a3b 100644 --- a/docs/developer-guide/Events.asciidoc +++ b/docs/developer-guide/Events.asciidoc @@ -1,21 +1,21 @@ == Events [[events-top-level-section,Events Section]] -Most events in Codename One are routed via the high-level events (e.g. action listener) but sometimes you need -access to low-level events (e.g. when drawing via Graphics) that provide more fine grained access. Typically +Most events in Codename One are routed via the high-level events (for example, action listener) but sometimes you need +access to low-level events (for example, when drawing via Graphics) that provide more fine grained access. Typically working with the higher level events is far more potable since it might map to different functionality on different devices. -=== high-level Events +=== High-level events -high-level events are broadcast using an `addListener`/`setListener` - publish/subscribe system. Most of them are channeled via the https://www.codenameone.com/javadoc/com/codename1/ui/util/EventDispatcher.html[EventDispatcher] class which further simplifies that and makes sending events correctly far easier. +high-level events are broadcast using an `addListener`/`setListener` - publish/subscribe system. Most of them are channeled via the https://www.codenameone.com/javadoc/com/codename1/ui/util/EventDispatcher.html[EventDispatcher] class which further simplifies that and makes sending events far easier. TIP: All events are fired on the Event Dispatch Thread, the `EventDispatcher` makes sure of that. -==== Chain Of Events +==== Chain of events Since all events fire on the EDT some complexities occur. E.g.: -you have two listeners monitoring the same event (or related events e.g. pointer event and button click event both of which will fire when the button is touched). +you have two listeners monitoring the same event (or related events for example, pointer event and button click event both of which will fire when the button is touched). When the event occurs you can run into a scenario like this: @@ -25,15 +25,15 @@ When the event occurs you can run into a scenario like this: This happens because events are processed in-order per cycle. Since the old EDT cycle is stuck (because of the `Dialog`) the rest of the events within the cycle can't complete. New events are in a new EDT cycle so they can finish fine! -A workaround to this issue is to wrap the code in a `callSerially`, you shouldn't do this universally as this can create a case of shifting the problem to the next EDT cycle. However, using callSerially will allow the current cycle to flush which should help. +A workaround to this issue is to wrap the code in a `callSerially`, you shouldn't do this universally as this can create a case of shifting the problem to the next EDT cycle. But, using callSerially will allow the current cycle to flush which should help. Another workaround for the issue is avoiding blocking calls within an event chain. -==== Action Events +==== Action events -The most common high-level event is the https://www.codenameone.com/javadoc/com/codename1/ui/events/ActionListener.html[ActionListener] which allows binding a generic action event to pretty much anything. This is so ubiquitous in Codename One that it's even used for networking (as a base class) and for some of the low-level event options. +The most common high-level event is the https://www.codenameone.com/javadoc/com/codename1/ui/events/ActionListener.html[ActionListener] which allows binding a generic action event to pretty much anything. This is so ubiquitous in Codename One that it's even used for networking (as a base class) and for some low-level event options. -E.g. you can bind an event callback for a https://www.codenameone.com/javadoc/com/codename1/ui/Button.html[Button] by using: +For example, you can bind an event callback for a https://www.codenameone.com/javadoc/com/codename1/ui/Button.html[Button] by using: [source,java] ---- @@ -55,32 +55,32 @@ b.addActionListener((ev) -> }); ---- -Notice that the click will work whether the button was touched using a mouse, finger or keypad shortcut seamlessly with an action listener. Many components work with action events e.g. buttons, text components, slider etc. +Notice that the click will work whether the button was touched using a mouse, finger or keypad shortcut seamlessly with an action listener. Many components work with action events for example, buttons, text components, slider etc. There are quite a few types of high-level event types that are more specific to requirements. -===== Types Of Action Events +===== Types of action events -When an action event is fired it's given a type, however this type might change as the event evolves e.g. a command triggered by a pointer event won't include details of the original pointer event. +When an action event is fired it's given a type, but this type might change as the event evolves for example, a command triggered by a pointer event won't include details of the original pointer event. You can get the event type from https://www.codenameone.com/javadoc/com/codename1/ui/events/ActionEvent.html#getEventType--[getEventType()], this also gives you a rather exhaustive list of the possible event types for the action event. Modern gesture components also publish custom action types. For example, https://www.codenameone.com/javadoc/com/codename1/ui/SwipeableContainer.html[SwipeableContainer] dispatches https://www.codenameone.com/javadoc/com/codename1/ui/events/ActionEvent.Type.html#Swipe-[ActionEvent.Type.Swipe] when the top component is fully opened, allowing code to react to swipe gestures without monitoring low-level drags. Listening for these higher level events keeps gesture handling portable across touch and desktop targets. -===== Source Of Event +===== Source of event -`ActionEvent` has a source object, what that source is depends heavily on the event type. For most component based events this is the component but there are some nuances. +`ActionEvent` has a source object, what that source is depends on the event type. For most component based events this is the component but there are some nuances. The `getComponent()` method might not get the actual component. In case of a lead component such as https://www.codenameone.com/javadoc/com/codename1/components/MultiButton.html[MultiButton] the underlying https://www.codenameone.com/javadoc/com/codename1/ui/Button.html[Button] will be returned and not the `MultiButton` itself. To get the component that you would logically think of as the source component use the `getActualComponent()` method. -===== Event Consumption +===== Event consumption -An `ActionEvent` can be consumed, once consumed it will no longer proceed down the chain of event processing. This is useful for some cases where we would like to block behavior from proceeding down the path. +An `ActionEvent` can be consumed, once consumed it will no longer proceed down the chain of event processing. This is useful for some cases where you would like to block behavior from proceeding down the path. -E.g. the event dispatch thread allows us to listen to errors on the EDT using: +For example, the event dispatch thread allows you to listen to errors on the EDT using: [source,java] ---- @@ -102,13 +102,11 @@ Display.getInstance().addEdtErrorHandler((e) -> { }); ---- -Notice that you can check if an event was already consumed using the `isConsumed()` method but it's pretty unlikely that you will receive a consumed event as the system will usually stop sending it. +Notice that you can check if an event was already consumed using the `isConsumed()` method but it's pretty unlikely that you will receive a consumed event as the system will stop sending it. ===== NetworkEvent -https://www.codenameone.com/javadoc/com/codename1/io/NetworkEvent.html[NetworkEvent] is a subclass of `ActionEvent` that's passed to `actionPerformed` callbacks made in relation to generic network code. E.g. - -[source,java] +https://www.codenameone.com/javadoc/com/codename1/io/NetworkEvent.html[NetworkEvent] is a subclass of `ActionEvent` that's passed to `actionPerformed` callbacks made in relation to generic network code. For example, [source,java] ---- NetworkManager.getInstance().addErrorListener(new ActionListener() { public void actionPerformed(NetworkEvent ev) { @@ -126,7 +124,7 @@ NetworkManager.getInstance().addErrorListener((ev) -> { }); ---- -The `NetworkEvent` allows the networking code to reuse the `EventDispatcher` infrastructure and to simplify event firing thru the EDT. But you should notice that some code might not be equivalent e.g. we could do this to read the input stream: +The `NetworkEvent` allows the networking code to reuse the `EventDispatcher` infrastructure and to simplify event firing thru the EDT. But you should notice that some code might not be equivalent for example, you could do this to read the input stream: [source,java] ---- @@ -149,22 +147,22 @@ r.addResponseListener((e) -> { }); ---- -These seem similar but they have one important distinction. The latter code is invoked on the EDT, so if `data` is big it might slow down processing significantly. The `ConnectionRequest` is invoked on the network thread and so can process any amount of data without slowing down the UI significantly. +These seem similar but they have one important distinction. The latter code is invoked on the EDT, so if `data` is big it might slow down processing. The `ConnectionRequest` is invoked on the network thread and so can process any amount of data without slowing down the UI. -===== MessageEvent And The Cross-Platform Message Bus +===== MessageEvent and the Cross-Platform message bus Bridging to native code often means passing messages between Codename One Java and platform specific code. The cross-platform message bus provides a high-level abstraction for that by letting you post messages to the native layer and subscribe for messages that originate there. Use https://www.codenameone.com/javadoc/com/codename1/ui/Display.html#postMessage-com.codename1.ui.events.MessageEvent-[Display.postMessage()] to send an https://www.codenameone.com/javadoc/com/codename1/ui/events/MessageEvent.html[MessageEvent] to the platform, and https://www.codenameone.com/javadoc/com/codename1/ui/Display.html#addMessageListener-com.codename1.ui.events.ActionListener-[Display.addMessageListener()] to receive events that arrive from native code, JavaScript bridges, or background services. Message events include helpers like `isPromptForAudioRecorder()`, `isPromptForAudioPlayer()`, and `getPromptPromise()` that allow you to hook into permission prompts emitted by the JavaScript port so custom UI can respond to platform requests while keeping the event dispatch on the EDT. This API complements `NetworkEvent` by covering native-to-Java messaging without requiring direct low-level callbacks. ==== DataChangeListener -The https://www.codenameone.com/javadoc/com/codename1/ui/events/DataChangedListener.html[DataChangedListener] is used in several places to indicate that the underlying model data has changed: +The https://www.codenameone.com/javadoc/com/codename1/ui/events/DataChangedListener.html[DataChangedListener] is used in several places to show that the underlying model data has changed: * https://www.codenameone.com/javadoc/com/codename1/ui/TextField.html[TextField] - the text field provides an action listener but that "fires" when the data input is complete. `DataChangeListener` fires with every key entered and thus allows functionality such as "auto complete" and is indeed used internally in the Codename One AutoCompleteTextField. * https://www.codenameone.com/javadoc/com/codename1/ui/table/TableModel.html[TableModel] & https://www.codenameone.com/javadoc/com/codename1/ui/list/ListModel.html[ListModel] - the model for the https://www.codenameone.com/javadoc/com/codename1/ui/table/Table.html[Table] class notifies the view that its content has changed via this -event, thus allowing the UI to refresh properly. +event, thus allowing the UI to refresh. // HTML_ONLY_START There is a exhaustive example of search that's implemented using the `DataChangedListener` in the https://www.codenameone.com/manual/components.html#Advanced-search-code[Toolbar section]. @@ -176,7 +174,7 @@ There is a exhaustive example of search that's implemented using the `DataChange ==== FocusListener -The focus listener allows us to track the currently "selected" or focused component. It isn't as useful as it used to be in feature phones. +The focus listener allows you to track the "selected" or focused component. It isn't as useful as it used to be in feature phones. You can bind a focus listener to the `Component` itself and receive an event when it gained focus, or you can bind the listener to the `Form` and receive events for every focus change event within the hierarchy. @@ -184,9 +182,9 @@ You can bind a focus listener to the `Component` itself and receive an event whe https://www.codenameone.com/javadoc/com/codename1/ui/events/ScrollListener.html[ScrollListener] allows tracking scroll events so UI elements can be adapted if necessary. -Normally scrolling is seamless and this event isn't necessary, however if developers wish to "shrink" or "fade" an element on scrolling this interface can be used to achieve that. Notice that you should bind the scroll listener to the actual scrollable component and not to an arbitrary component. +Scrolling is seamless and this event isn't necessary, but if developers wish to "shrink" or "fade" an element on scrolling this interface can be used to achieve that. Notice that you should bind the scroll listener to the actual scrollable component and not to an arbitrary component. -E.g. in this code from the `Flickr` demo the https://www.codenameone.com/javadoc/com/codename1/ui/Toolbar.html[Toolbar] is faded based on scroll position: +For example, in this code from the `Flickr` demo the https://www.codenameone.com/javadoc/com/codename1/ui/Toolbar.html[Toolbar] is faded based on scroll position: [source,java] ---- @@ -221,13 +219,13 @@ NOTE: There is a better way of implementing this exact effect using title animat ==== SelectionListener -The https://www.codenameone.com/javadoc/com/codename1/ui/events/SelectionListener.html[SelectionListener] event is mostly used to broadcast list model selection changes to the list view. Since list supports the `ActionListener` event callback its usually the better option since it's more coarse grained. +The https://www.codenameone.com/javadoc/com/codename1/ui/events/SelectionListener.html[SelectionListener] event is used to broadcast list model selection changes to the list view. Since list supports the `ActionListener` event callback its the better option since it's more coarse grained. -`SelectionListener` gets fired too often for events and that might result in a performance penalty. When running on non-touch devices list selection could be changed with the keypad and a specific fire button click would fire the action event, for those cases `SelectionListener` made a lot of sense. However, in touch devices this API isn't as useful. +`SelectionListener` gets fired too often for events and that might result in a performance penalty. When running on non-touch devices list selection could be changed with the keypad and a specific fire button click would fire the action event, for those cases `SelectionListener` made a lot of sense. But, in touch devices this API isn't as useful. ==== StyleListener -https://www.codenameone.com/javadoc/com/codename1/ui/events/StyleListener.html[StyleListener] allows components to track changes to the style objects. E.g. if the developer does something like: +https://www.codenameone.com/javadoc/com/codename1/ui/events/StyleListener.html[StyleListener] allows components to track changes to the style objects. For example, if the developer does something like: [source,java] ---- @@ -237,15 +235,15 @@ cmp.getUnselectedStyle().setFgColor(0xffffff); This will trigger a style event that will eventually lead to the component being repainted. This is quite important for the component class but not a important event for general user code. it's recommended that developers don't bind a style listener. -==== Component State Change Events +==== Component state change events -Component instances now publish lifecycle hooks that fire when they become initialized on a form and when they are removed. You can subscribe with https://www.codenameone.com/javadoc/com/codename1/ui/Component.html#addStateChangeListener-com.codename1.ui.events.ActionListener-[Component.addStateChangeListener()] to receive https://www.codenameone.com/javadoc/com/codename1/ui/events/ComponentStateChangeEvent.html[ComponentStateChangeEvent] instances that indicate whether the component is transitioning to the initialized state. This is especially useful for running setup or teardown logic alongside focus, scroll, and selection listeners. +Component instances now publish lifecycle hooks that fire when they become initialized on a form and when they are removed. You can subscribe with https://www.codenameone.com/javadoc/com/codename1/ui/Component.html#addStateChangeListener-com.codename1.ui.events.ActionListener-[Component.addStateChangeListener()] to receive https://www.codenameone.com/javadoc/com/codename1/ui/events/ComponentStateChangeEvent.html[ComponentStateChangeEvent] instances that show whether the component is transitioning to the initialized state. This is useful for running setup or teardown logic alongside focus, scroll, and selection listeners. -==== Event Dispatcher +==== Event dispatcher When creating your own components and objects you sometimes want to broadcast your own events, for that purpose Codename One has the https://www.codenameone.com/javadoc/com/codename1/ui/util/EventDispatcher.html[EventDispatcher] class which saves a lot of coding effort in this regard. -E.g. if you wish to provide an https://www.codenameone.com/javadoc/com/codename1/ui/events/ActionListener.html[ActionListener] event for components you can add this to your class: +For example, if you wish to provide an https://www.codenameone.com/javadoc/com/codename1/ui/events/ActionListener.html[ActionListener] event for components you can add this to your class: [source,java] ---- @@ -269,17 +267,17 @@ private void fireEvent(ActionEvent ev) { ---- -=== low-level Events +=== Low-level events low-level events map to "system" events directly. Touch events are considered low-level since they might expose platform specific nuances to your code. -E.g. one platform might send a large number of events during drag while another might send a few. Normally the high-level event handling hides those complexities but some of them trickle down into the low-level event handling. +For example, one platform might send many events during drag while another might send a few. The high-level event handling hides those complexities but some of them trickle down into the low-level event handling. -TIP: Codename One tries to hide some of the complexities from the low-level events as well. However, due to the nature of the event types it's a more challenging task. +TIP: Codename One tries to hide some complexities from the low-level events as well. But, due to the nature of the event types it's a more challenging task. low-level events can be bound in one of 3 ways: -* Use one of the add listener methods in https://www.codenameone.com/javadoc/com/codename1/ui/Form.html[Form] e.g. `addPointerPressedListener`. +* Use one of the add listener methods in https://www.codenameone.com/javadoc/com/codename1/ui/Form.html[Form] for example, `addPointerPressedListener`. * Override one of the event callbacks on `Form` @@ -291,7 +289,7 @@ Each of those has advantages and disadvantages, specifically: * 'Form' based events and callbacks deliver pointer events in the 'Form' coordinate space. * 'Component' based events require focus -* 'Form' based events can block existing functionality from proceeding thru the event chain e.g. you can avoid calling super in a form event and thus block other events from happening (e.g. block a listener or component event from triggering). +* 'Form' based events can block existing functionality from proceeding thru the event chain for example, you can avoid calling super in a form event and thus block other events from happening (for example, block a listener or component event from triggering). .Event type map [cols="4*"] @@ -308,9 +306,9 @@ Each of those has advantages and disadvantages, specifically: |No |=== -==== low-level Event Types +==== Low-level event types -There are two basic types of low-level events: Key and Pointer. +two basic types of low-level events: Key and Pointer. IMPORTANT: Key events are relevant to physical keys and won't trigger on virtual keyboard keys, to track those use a https://www.codenameone.com/javadoc/com/codename1/ui/TextField.html[TextField] with a `DataChangeListener` as mentioned above. @@ -333,19 +331,19 @@ public void keyReleased(int keyCode) public void keyRepeated(int keyCode) ---- -Notice that most pointer events have a version that accepts an array as an argument, this allows for multi-touch event handling by sending all the currently touched coordinates. Desktop and pen-enabled devices can also trigger hover events without a press. To respond to those you can override the `pointerHover*` callbacks on `Form` or `Component`, which are invoked before a button receives focus or a drag begins on those platforms. +Notice that most pointer events have a version that accepts an array as an argument, this allows for multi-touch event handling by sending all the touched coordinates. Desktop and pen-enabled devices can also trigger hover events without a press. To respond to those you can override the `pointerHover*` callbacks on `Form` or `Component`, which are invoked before a button receives focus or a drag begins on those platforms. -While you can override `longPointerPress`, there is usually no need. The dedicated https://www.codenameone.com/javadoc/com/codename1/ui/Component.html#addLongPressListener-com.codename1.ui.events.ActionListener-[Component.addLongPressListener()] helper wires long press detection into an action listener so you can keep gesture logic in the high-level API. +While you can override `longPointerPress`, there is no need. The dedicated https://www.codenameone.com/javadoc/com/codename1/ui/Component.html#addLongPressListener-com.codename1.ui.events.ActionListener-[Component.addLongPressListener()] helper wires long press detection into an action listener so you can keep gesture logic in the high-level API. -Drag and drop lifecycles also expose a completion hook. When `Component.addDragFinishedListener()` is registered it receives https://www.codenameone.com/javadoc/com/codename1/ui/events/ActionEvent.Type.html#DragFinished-[ActionEvent.Type.DragFinished] once the framework has completed its cleanup, allowing you to reset state or trigger follow-up actions that should occur after the drag image is hidden. +Drag lifecycles also expose a completion hook. When `Component.addDragFinishedListener()` is registered it receives https://www.codenameone.com/javadoc/com/codename1/ui/events/ActionEvent.Type.html#DragFinished-[ActionEvent.Type.DragFinished] once the framework has completed its cleanup, allowing you to reset state or trigger follow-up actions that should occur after the drag image is hidden. -==== Drag Event Sanitation +==== Drag event sanitation -Drag events are quite difficult to handle properly across devices. Some devices send a ridiculous number of events for even the lightest touch while others send too little. It seems like too many drag events wouldn't be a problem, however if we drag over a button then it might disable the buttons action event (since this might be the user trying to scroll). +Drag events are quite difficult to handle across devices. Some devices send a ridiculous number of events for even the lightest touch while others send too little. It seems like too many drag events wouldn't be a problem, but if you drag over a button then it might disable the buttons action event (since this might be the user trying to scroll). -Drag sensitivity is about the component being dragged which is why you've the method `getDragRegionStatus` that allows us to "hint" to the drag API whether you're interested in drag events or not and if so in which directional bias. +Drag sensitivity is about the component being dragged which is why you've the method `getDragRegionStatus` that allows you to "hint" to the drag API whether you're interested in drag events or not and if so in which directional bias. -E.g. if our component is a painting app where you're trying to draw using drag gestures we would use code such as: +For example, if your component is a painting app where you're trying to draw using drag gestures you would use code such as: [source,java] ---- @@ -356,7 +354,7 @@ public class MyComponent extends Component { } ---- -This indicates that we want all drag events on both AXIS to be sent as soon as possible. Notice that this doesn't completely disable event sanitation. +This indicates that you want all drag events on both AXIS to be sent as soon as possible. Notice that this doesn't disable event sanitation. === BrowserNavigationCallback @@ -364,7 +362,7 @@ The https://www.codenameone.com/javadoc/com/codename1/ui/events/BrowserNavigatio IMPORTANT: The callback method of this interface is invoked off the EDT! You must **NEVER** block this method and must not access UI or Codename One sensitive elements in this method! -The browser navigation callback is invoked directly from the native web component as it navigates to a new page. Because of that it's invoked on the native OS thread and gives us a unique opportunity to handle the navigation ourselves as we see fit. that's why it MUST be invoked on the native thread, since the native browser is pending on our response to that method, spanning an invokeAndBlock/callSerially would be to slow and would bog down the browser. +The browser navigation callback is invoked directly from the native web component as it navigates to a new page. Because of that it's invoked on the native OS thread and gives you a unique opportunity to handle the navigation ourselves as you see fit. that's why it MUST be invoked on the native thread, since the native browser is pending on your response to that method, spanning an invokeAndBlock/callSerially would be to slow and would bog down the browser. You can use the browser navigation callback to change the UI or even to invoke Java code from JavaScript code e.g.: diff --git a/docs/developer-guide/Index.asciidoc b/docs/developer-guide/Index.asciidoc index 7f7984ce88..3a530f23b7 100644 --- a/docs/developer-guide/Index.asciidoc +++ b/docs/developer-guide/Index.asciidoc @@ -1,6 +1,6 @@ == Introduction -Codename One is a Write Once Run Anywhere mobile development platform for Java/Kotlin developers. It fits naturally into modern Maven-capable IDEs such as IntelliJ IDEA, NetBeans, VS Code, and Eclipse, and you can also drive it entirely from the command line. +Codename One is a Write Once Run Anywhere mobile development platform for Java/Kotlin developers. It fits into modern Maven-capable IDEs such as IntelliJ IDEA, NetBeans, VS Code, and Eclipse, and you can also drive it entirely from the command line. Codename One's mission statement is: @@ -16,7 +16,7 @@ The things that make Codename One stand out from other tools in this field are: * Write Once Run Anywhere support with no special hardware requirements and 100% code reuse (((Reuse))) * Compiles Java/Kotlin into native code for iOS, Android and even JavaScript/PWA * Open Source and Free with commercial backing/support -* Easy to use with 100% portable Drag and Drop GUI builder +* Easy to use with 100% portable Drag GUI builder * Full access to underlying native OS capabilities using the native OS programming language (for example: Objective-C) without compromising portability * Provides full control over every pixel on the screen * Lets you use native widgets (views) and mix them with Codename One components within the same hierarchy (heavyweight/lightweight mixing) (((Widgets))) @@ -25,9 +25,9 @@ The things that make Codename One stand out from other tools in this field are: Codename One can trace its roots to the open source LWUIT project started at Sun Microsystem in 2007 by Chen Fishbein (co-founder of Codename One). It has been under constant development for over a decade. -=== Build Cloud +=== Build cloud -The build cloud approach to mobile development is one of the things that makes Codename One stand out. iOS native development requires a Mac with Xcode. Windows native development requires a Windows machine. To make matters worse, Apple, Google, and Microsoft change their tools regularly. +The build cloud approach to mobile development is one of the things that makes Codename One stand out. iOS native development requires a Mac with Xcode. Windows native development requires a Windows machine. To make matters worse, Apple, Google, and Microsoft change their tools. That makes it hard to keep up. @@ -35,9 +35,9 @@ When you develop an app in Codename One, use the built-in simulator for running IMPORTANT: Codename One doesn't send source code to the build cloud, compiled bytecode! -==== Why Build Servers? +==== Why build servers -The build servers let you build native iOS apps without a Mac and native Windows apps without a Windows machine. They remove the need to install and update complex toolchains and simplify the process of building a native app to a right click. +The build servers let's build native iOS apps without a Mac and native Windows apps without a Windows machine. They remove the need to install and update complex toolchains and simplify the process of building a native app to a right click. Even though the build servers streamline delivery, Codename One also supports fully local builds. You can install the toolchain on your own hardware and follow the workflows in <> and <> to compile, package, and test apps without leaving your desktop environment. @@ -45,7 +45,7 @@ For example, because native iOS applications require a Mac with a recent version Subscribers can receive the C source code back using the include-sources feature of Codename One and use those sources for benchmarking and debugging on devices. -The same is true for most other platforms. On Android and Java SE, Codename One runs the standard Java code, mostly unchanged. +The same is true for most other platforms. On Android and Java SE, Codename One runs the standard Java code, unchanged. .Java Language Level Support **** @@ -54,29 +54,29 @@ Historically, Java 8 syntax was supported through https://github.com/orfjackal/r Today Codename One supports Java 25 level bytecode in https://github.com/codenameone/CodenameOne/tree/master/vm[ParparVM] and Android supports Java 17 level bytecode natively. This lets Codename One support Java 17 seamlessly, and newer versions enable Java 25 level support through client side bytecode mutation. **** -==== How does Codename One Work? +==== How does Codename One work -Codename One uses a SaaS-based approach so the information in this appendix might (and probably will) change in the future to accommodate improved architectures. This appendix includes this information for reference, you don't need to understand this in order to follow the content of the book... +Codename One uses a SaaS-based approach so the information in this appendix might (and probably will) change in the future to accommodate improved architectures. This appendix includes this information for reference, you don't need to understand this to follow the content of the book... Since Android is already based on Java, Codename One is already native to Android and works with the Android VM (ART/Dalvik). -On iOS, Codename One built and open-sourced ParparVM, which is a conservative VM. ParparVM features a concurrent, non-blocking GC and is written entirely in Java/C. ParparVM is a transpiler that generates C source code matching the given Java bytecode. This means that an Xcode project is generated and compiled on the build servers. It's as if you hand-coded a native app and is thus future-proof against changes that Apple introduces. For example, Apple migrated to 64-bit and later introduced bitcode support to iOS. ParparVM needed no modifications to comply with those changes. +On iOS, Codename One built and open-sourced ParparVM, which is a conservative VM. ParparVM features a concurrent, non-blocking GC and is written entirely in Java/C. ParparVM is a transpiler that generates C source code matching the given Java bytecode. This means that an Xcode project is generated and compiled on the build servers. It's as if you hand-coded a native app and is thus future-proof against changes that Apple introduces. For example, Apple migrated to 64-bit and later introduced bitcode support to iOS. ParparVM needed no modifications to meet those changes. NOTE: Codename One translates the bytecode to C, which is faster than Swift/Objective-C. The port code that invokes iOS API's is hand coded in Objective-C -Codename One previously offered a UWP (Universal Windows Platform) target based on iKVM. That target was discontinued in release 7.0.229 and is preserved as historical context in older documentation and blog posts. +Codename One earlier offered a UWP (Universal Windows Platform) target based on iKVM. That target was discontinued in release 7.0.229 and is preserved as historical context in older documentation and blog posts. JavaScript build targets use TeaVM to do the translation statically. TeaVM supports threading using JavaScript by breaking the app down in a rather elaborate way. To support the complex UI Codename One uses the HTML5 Canvas API which allows absolute flexibility for building applications. For desktop builds Codename One uses `javapackager`, since both Macs and Windows machines are available in the cloud, the platform-specific nature of `javapackager` isn't a problem. -===== Lightweight Architecture +===== Lightweight architecture -What makes Codename One stand out is the approach it takes to UI: "`lightweight architecture`". +What makes Codename One stand out is the approach it takes to UI: "`lightweight architecture.`" Lightweight architecture is the "`not so secrete sauce`" to Codename One's portability. Essentially it means all the components/widgets in Codename One are written in Java. Thus their behavior is consistent across all platforms and they are fully customizable from the developer code as they don't rely on OS internal semantics. This allows developers to preview the application accurately in the simulators and GUI builders. -One of the big accomplishments in Codename One is its unique ability to embed "`heavyweight`" widgets into place among the "`lightweights`". This is crucial for apps such as Uber where the cars and widgets on top are implemented as Codename One components yet below them you've the native map component. +One of the big accomplishments in Codename One is its unique ability to embed "`heavyweight`" widgets into place among the "`lightweights.`" This is crucial for apps such as Uber where the cars and widgets on top are implemented as Codename One components yet below them you've the native map component. Codename One achieves fast performance by drawing using the native gaming API's of most platforms for example: OpenGL ES on iOS. The core technologies behind Codename One are all open source including most of the stuff developed by Codename One itself, for example: ParparVM but also the full library, platform ports, designer tool, device skins etc. @@ -93,19 +93,19 @@ NOTE: XMLVM could guarantee that as well, but it's no longer maintained and lack The key advantages of ParparVM over other approaches are: -- *Truly Native* -- since code is translated to C rather than directly to ARM or LLVM code, the app is "more native". It uses the official tools and approaches from Apple and can benefit from their advancements. For example: bitcode changes or profiling toolchain. +- *Truly Native:* since code is translated to C rather than directly to ARM or LLVM code, the app is "more native." It uses the official tools and approaches from Apple and can gain from their advancements. For example: bitcode changes or profiling toolchain. -- *Smaller Class Library* -- ParparVM includes a small segment of the full JavaAPI's resulting in final binaries that are smaller than the alternatives by orders of magnitude. This maps directly to performance and memory overhead. +- *Smaller Class Library:* ParparVM includes a small segment of the full JavaAPI's resulting in final binaries that are smaller than the alternatives by orders of size. This maps directly to performance and memory overhead. -- *Simple and Extensible* -- to work with ParparVM you need a basic understanding of C. Unlike other tools that require deep understanding of ARM assembly and LLVM bitcode. This is crucial for the fast-moving world of mobile development, as Apple changes things left and right, you need a more agile VM. +- *Simple and Extensible:* to work with ParparVM you need a basic understanding of C. Unlike other tools that require deep understanding of ARM assembly and LLVM bitcode. This is crucial for the fast-moving world of mobile development, as Apple changes things left and right, you need a more agile VM. -===== Historical Windows Ports +===== Historical windows ports Codename One experimented with many Windows ports over the years, including the legacy Windows Phone target and a later UWP (Universal Windows Platform) target based on iKVM. NOTE: The UWP target was discontinued in release 7.0.229 and is no longer part of the supported build targets. Older references to it in historical material are retained for archival context. -===== JavaScript Port +===== JavaScript port The JavaScript port of Codename One is based on the work of the http://teavm.org:[TeaVM project]. The team behind TeaVM effectively built a JVM that translates Java bytecode into JavaScript source code while maintaining threading semantics with an imaginative approach. @@ -132,7 +132,7 @@ Codename One was started by Chen Fishbein and Shai Almog who authored the Open S In 2012 Shai and Chen formed Codename One as they left Oracle. The project has taken many of the basic concepts developed within the LWUIT project and adapted them to the smartphone world which is still experiencing similar issues to the device fragmentation of the old J2ME phones. -=== Core Concepts of Mobile Programming +=== Core concepts of mobile programming Before you proceed, I would like to explain some universal core concepts of mobile programming that might not be intuitive. These are universal concepts that apply to mobile programming regardless of the tools you're using. @@ -140,14 +140,14 @@ You can skip this section if you feel you're familiar enough with the core probl ==== Density -Density is also known as DPI (Dots Per Inch) or PPI (pixels or points per inch). Density is confusing, unintuitive and might collide with common sense. For example, an iPhone 7 plus has a resolution of `1080x1920` pixels and a PPI of `401` for a 5-inch screen. On the other hand, an iPad 4 has `1536x2048` pixels with a PPI of `264` on a `9.7` inch screen... Smaller devices can have higher resolutions! +Density is also known as DPI (Dots Per Inch) or PPI (pixels or points per inch). Density is confusing, unintuitive and might collide with common sense. For example, an iPhone 7 plus has a resolution of `1080x1920` pixels and a PPI of `401` for a 5-inch screen. But, an iPad 4 has `1536x2048` pixels with a PPI of `264` on a `9.7` inch screen... Smaller devices can have higher resolutions! -As the following figure shows, if a Pixel 2 XL had pixels the size of an iPad, it would have been twice the size of that iPad. While in reality, it's nearly half the height of the iPad! +As the following figure shows, if a Pixel 2 XL had pixels the size of an iPad, it would have been twice the size of that iPad. While in reality, it's half the height of the iPad! .Device Density vs. Resolution image::img/dpi.png[Device Density vs Resolution] -Differences in density can be extreme. A second generation iPad has 132 PPI, where modern phones have PPI that is over 600 dpi. +Differences in density can be extreme. A second generation iPad has 132 PPI, where modern phones have PPI that's over 600 dpi. Low resolution images on high PPI devices will look either small or pixelated. High resolution images on low PPI devices will look huge, overscaled (artifacts) and will consume too much memory. @@ -158,33 +158,33 @@ The exact same image will look different on each device, sometimes to a comical This also highlights the need for working with measurements other than pixels. Codename One supports millimeters (or dips) as a unit of measurement. This is highly convenient and is a better representation of size when dealing with mobile devices. -But there is a bigger conceptual issue involved. You need to build a UI that adapts to the wide differences in form factors. You might have fewer pixels on an iPad, but because of its physical size, you would expect the app to cram more information into that space so the app won't feel like a blown-up phone application. There are many strategies to address that, but one of the first steps is in the layout managers. (((Layouts, Layout))) +However, there is a bigger conceptual issue involved. You need to build a UI that adapts to the wide differences in form factors. You might have fewer pixels on an iPad, but because of its physical size, you would expect the app to cram more information into that space so the app won't feel like a blown-up phone application. many strategies to address that, but one of the first steps is in the layout managers. (((Layouts, Layout))) I will discuss the layout managers in depth in Chapter 2, but the core concept is that they decide where a UI element is placed based on generic logic. That way, the user interface can adapt automatically to the huge variance in display size and density. -==== Touch Interface +==== Touch interface -The fact that mobile devices use a touch interface today isn't news... But the implications of that aren't immediately obvious to some developers. +The fact that mobile devices use a touch interface today isn't news... However, the implications of that aren't obvious to some developers. -UI elements need to be finger sized and heavily spaced. Otherwise, you risk the "`fat finger`" effect. That means spacing should be in millimeters and not in pixels due to device density. +UI elements need to be finger sized and spaced. Otherwise, you risk the "`fat finger`" effect. That means spacing should be in millimeters and not in pixels due to device density. Scrolling poses another challenge in touch-based interfaces. In desktop applications, it's common to nest scrollable items. For example, in touch interfaces, the scrolling gesture doesn't allow such a nuance. Furthermore, scrolling on both the horizontal and vertical axis (side scrolling) can be inconvenient in touch-based interfaces. -==== Device Fragmentation +==== Device fragmentation -Some developers single out this wide range of resolutions and densities as "`device fragmentation`". While it does contribute to development complexity, for the most part, it isn't a challenging problem to overcome. +Some developers single out this wide range of resolutions and densities as "`device fragmentation.`" While it does contribute to development complexity, it isn't a challenging problem to overcome. -Densities aren't the cause of device fragmentation. Device fragmentation is caused by many OS versions with different behaviors. This is clear on Android and for the most part relates to the slow rollout of Android vendor versions compared to Google rollout. For example, 7 months after the Android 8 (Oreo) release in 2018, it was still available on 1.1% of the devices. The damning statistic is that 12% of the devices in mid 2018 run Android 4.4 Kitkat released in 2013! (((Google))) +Densities aren't the cause of device fragmentation. Device fragmentation is caused by many OS versions with different behaviors. This is clear on Android and relates to the slow rollout of Android vendor versions compared to Google rollout. For example, 7 months after the Android 8 (Oreo) release in 2018, it was still available on 1.1% of the devices. The damning statistic is that 12% of the devices in mid 2018 run Android 4.4 Kitkat released in 2013! (((Google))) -This makes QA difficult as the disparity between these versions is pretty big. These numbers will be out of date by the time you read this, but the core problem remains. It's hard to get all device manufacturers on the same page, so this problem will probably remain in the foreseeable future despite everything. +This makes QA difficult as the disparity between these versions is pretty big. These numbers will be out of date by the time you read this, but the core problem remains. It's hard to get all device manufacturers aligned, so this problem will probably remain in the foreseeable future despite everything. ==== Performance Besides the obvious need for performance and smooth animation within a mobile app, there are a couple of performance related issues that might not be intuitive to new developers: size and power. -===== App Size +===== App size -Apps are installed and managed through stores. This poses some restrictions about what an app can do. But it also creates a huge opportunity. Stores manage automatic update and to some degree the marketing/monetization of the app. +Apps are installed and managed through stores. This poses some restrictions about what an app can do. However, it also creates a huge opportunity. Stores manage automatic update and to some degree the marketing/monetization of the app. A good mobile app is updated once a month and sometimes even once a week. Since the app downloads automatically from the store, this can be a huge benefit: @@ -193,28 +193,28 @@ A good mobile app is updated once a month and sometimes even once a week. Since If an app is big, it might not update over a cellular network connection. Google and Apple have restrictions on automatic updates over cellular networks to preserve battery life and data plans. A large app might negatively impact users perception of the app and trigger uninstalls for example, when a phone is low on available space. -===== Power Drain +===== Power drain -Desktop developers rarely think about power usage within their apps. In mobile development, this is a crucial concept. Modern device OS's have tools that highlight misbehaving applications, and this can lead to bad reviews. +Desktop developers think about power usage within their apps. In mobile development, this is a crucial concept. Modern device OS's have tools that highlight misbehaving applications, and this can lead to bad reviews. -Code that loops forever while waiting for input will block the CPU from sleeping and slowly drain the battery. +Code that loops forever while waiting for input will block the CPU from sleeping and drain the battery. Worse. Mobile OS's kill applications that drain the battery. If the app is draining the battery and is minimized, (for example, during an incoming call) the app could be killed. This will impact app performance and usability. -==== Sandbox and Permissions +==== Sandbox and permissions Apps installed on the device are "sandboxed" to a specific area so they won’t harm the device or its functionality. The filesystem of mobile applications is restricted so one application can’t access the files of another application. Things that most developers take for granted on the desktop such as a "file picker" or accessing the image folder don’t work on devices! This means that when your application works on a file, it belongs to your application. To share the file with a different application, you need to ask the operating system to do that for you. -Furthermore, some features require a "permission" prompt and in some cases require special flags in system files. Apps need to request permission to use sensitive capabilities, for example, Camera, Contacts etc. + +Furthermore, some features require a "permission" prompt and sometimes require special flags in system files. Apps need to request permission to use sensitive capabilities, for example, Camera, Contacts etc. + Historically, Android developers declared required permissions for an app and the user was prompted with permissions during installation. Android 6 adopted the approach used by iOS of prompting the user for permission when accessing a feature. -This means that in runtime a user might revoke a permission. A good example in the case of an Uber app is the location permission. If a user revokes that permission, the app might lose its location. +This means that in runtime a user might revoke a permission. A good example for an Uber app is the location permission. If a user revokes that permission, the app might lose its location. === Installing Codename One -IMPORTANT: Codename One requires either JDK 11 or JDK 8. Other JDK versions aren't supported at this time. +IMPORTANT: Codename One requires either JDK 11 or JDK 8. Other JDK versions aren't supported now. Codename One projects are built with Maven. Typical Maven targets such as `package`, `clean` and `install` work out of the box, but the Codename One integrations that ship with each IDE provide dedicated Run and Build actions for a smoother workflow. @@ -237,11 +237,11 @@ This command generates a project in the current directory. The folder name match Import the generated Maven project into your preferred IDE and use the Codename One Run in Simulator task from the IDE toolbar or Run/Debug buttons: -* *IntelliJ IDEA* – use *File > Open* on the project directory, then choose the Codename One Run in Simulator action from the toolbar or standard Run/Debug controls. -* *NetBeans* – use *File > Open Project*, select the generated Maven project, and rely on the Codename One toolbar actions to run and debug the simulator. -* *VS Code* – install the Java and Codename One extensions, open the folder, and trigger the Run in Simulator task from the command palette or the Run/Debug buttons. -* *Eclipse* – use *File > Import > Existing Maven Projects*, then use the Codename One launch shortcuts provided by the plugin for simulator and build tasks. -* *Command line* – invoke Maven goals directly whenever you need to integrate with CI/CD pipelines or scripting. +* *IntelliJ IDEA:* use *File > Open* on the project directory, then choose the Codename One Run in Simulator action from the toolbar or standard Run/Debug controls. +* *NetBeans:* use *File > Open Project*, select the generated Maven project, and rely on the Codename One toolbar actions to run and debug the simulator. +* *VS Code:* install the Java and Codename One extensions, open the folder, and trigger the Run in Simulator task from the command palette or the Run/Debug buttons. +* *Eclipse:* use *File > Import > Existing Maven Projects*, then use the Codename One launch shortcuts provided by the plugin for simulator and build tasks. +* *Command line:* invoke Maven goals directly whenever you need to integrate with CI/CD pipelines or scripting. For deeper coverage of the Maven goals, project structure, and automation tasks, continue with <>. @@ -252,23 +252,23 @@ NOTE: Arbitrary Maven dependencies probably won't work for Codename One. Many de Legacy Ant-based project instructions remain available for teams maintaining older codebases. New projects should follow the Maven workflows described in this guide. **** -==== Important Notes for New Projects +==== Important notes for new projects Before you get to the code there are few important things you need to understand about Codename One applications. * *App Name* - This is the name of the app and the main class, it's important to get this right as it's hard to change this value later -* *Package Name* - it's *crucial* you get this value right. Besides the difficulty of changing this after the fact, once an app is submitted to iTunes/Google Play with a specific package name this can't be changed! See the sidebar "Picking a Package Name". -* *Theme* - There are various types of built-in themes in Codename One, for simplicity recommend `Native` as it's a clean slate starting point +* *Package Name* - it's *crucial* you get this value right. Besides the difficulty of changing this after the fact, once an app is submitted to iTunes/Google Play with a specific package name this can't be changed! See the sidebar `Picking a Package Name`. +* *Theme* - There are various types of built-in themes in Codename One, for simplicity recommend `Native` as it's a minimal starting point .Picking a Package Name **** -Apple, Google and Microsoft identify applications based on their package names. If you use a domain that you don't own it's possible that someone else will use that domain and collide with you. In fact some developers left the default `com.mycompany` domain in place all the way into production in some cases. +Apple, Google and Microsoft identify applications based on their package names. If you use a domain that you don't own it's possible that someone else will use that domain and collide with you. In fact some developers left the default `com.mycompany` domain in place all the way into production sometimes. -This can cause difficulties when submitting to Apple, Google or Microsoft. Submitting to one of them is no guarantee of success when submitting to another. +This can cause difficulties when submitting to Apple, Google, or Microsoft. Submitting to one of them is no guarantee of success when submitting to another. -To come up with the right package name use a reverse domain notation. So if my website is `goodstuff.co.uk` my package name should start with `uk.co.goodstuff`. I highly recommend the following guidelines for package names: +To come up with the right package name use a reverse domain notation. if your website is `goodstuff.co.uk` your package name should start with `uk.co.goodstuff`. I highly recommend the following guidelines for package names: -* *Lower Case* - some OS's are case sensitive and handling a mistake in case is painful. The Java convention is lower case and I would recommend sticking to that although it isn't a requirement +* *Lower Case* - some OS's are case sensitive and handling a mistake in case is painful. The Java convention is lower case and I would recommend sticking to that although it isn't a need * *Avoid Dash and Underscore* - You can't use a dash character (`-`) for a package name in Java. Underscore (`_`) doesn't work for iOS. If you want more than one word just use a deeper package e.g.: `com.mydomain.deeper.meaningful.name` @@ -289,14 +289,14 @@ Use your IDE's Debug button with the Run in Simulator task to launch the simulat .Simulator vs. Emulator **** -Codename One ships with a simulator similarly to the iOS toolchain which also has a simulator. Android ships with an emulator. Emulators go the extra mile. They create a virtual machine that's compatible with the device CPU and then boot the full mobile OS within that environment. This provides an accurate runtime environment but is **painfully slow**. +Codename One ships with a simulator similarly to the iOS toolchain which also has a simulator. Android ships with an emulator. Emulators go further. They create a virtual machine that's compatible with the device CPU and then boot the full mobile OS within that environment. This provides a proper runtime environment but is **painfully slow**. Simulators rely on the fact that OS's are similar and so they leave the low-level details in place and just map the API behavior. Since Codename One relies on Java it can start simulating on top of the virtual machine on the desktop. That provides several advantages including fast development cycles and full support for all the development tools/debuggers you can use on the desktop. -Emulators make sense for developers who want to build OS level services e.g. screensavers or low-level services. Standard applications are better served by simulators. +Emulators make sense for developers who want to build OS level services for example, screensavers or low-level services. Standard applications are better served by simulators. **** -==== The Source Code Of The Hello World App +==== The source code of the hello world app After clicking finish in the new project wizard you've a `HelloWorld` project with a few default settings. I will break the class down to small pieces and explain each piece starting with the enclosing class: @@ -310,7 +310,7 @@ public class HelloWorld { // <1> } ---- -<1> This is the main class, it's the entry point to the app, notice it doesn't have a `main` method but rather callback which you will discuss soon +<1> This is the main class, it's the entry point to the app, notice it doesn't have a `main` method but rather callback which this guide covers soon <2> Forms are the "`top level`" UI element in Codename One. Only one `Form` is shown at a time and everything you see on the screen is a child of that `Form` @@ -350,7 +350,7 @@ public void init(Object context) { // <1> <6> In case of a network error the code in this block would run, you can customize it to handle networking errors effectively -<7> `consume()` swallows the event so it doesn't trigger other alerts, it generally means "`you got this`" +<7> `consume()` swallows the event so it doesn't trigger other alerts, it means "`you got this`" <8> Not all errors include an exception, if you've an exception you can log it with this code @@ -375,7 +375,7 @@ public void start() { } ---- -<1> If the app was minimized you usually don't want to do much, show the last `Form` of the application +<1> If the app was minimized you don't want to do much, show the last `Form` of the application <2> `current` is a `Form` which is the top most visual element. You can have one `Form` showing and you enforce that by using the `show()` method @@ -388,21 +388,21 @@ public void start() { .Title and Label in the UI image::img/codenameone-hello-world-title-label.png[Title and Label in the UI,scaledwidth=50%] -There are some complex ideas within this short snippet which I will address later in this chapter when talking about layout. The gist of it's that you create and show a `Form`. `Form` is the top level UI element, it takes over the whole screen. You can add UI elements to that `Form` object, in this case the `Label`. You use the `BoxLayout` to arrange the elements within the `Form` from top to the bottom vertically. +some complex ideas within this short snippet which I will address later in this chapter when talking about layout. The gist of it's that you create and show a `Form`. `Form` is the top level UI element, it takes over the whole screen. You can add UI elements to that `Form` object, in this case the `Label`. You use the `BoxLayout` to arrange the elements within the `Form` from top to the bottom vertically. .Application Lifecycle **** -A few years ago Romain Guy (a senior Google Android engineer) was on stage at the Google IO conference. He asked for a show of hands of people who understand the `Activity` lifecycle (`Activity` is similar to a Codename One main class). He then proceeded to jokingly call the audience members who lifted their hands "`liars`" claiming that after all his years in Google he still doesn't understand it... +A few years ago Romain Guy (a senior Google Android engineer) was on stage at the Google IO conference. He asked for a show of hands of people who understand the `Activity` lifecycle (`Activity` is like a Codename One main class). He then proceeded to jokingly call the audience members who lifted their hands "`liars`" claiming that after all his years in Google he still doesn't understand it... Lifecycle seems simple on the surface but hides a lot of nuance. Android's lifecycle is ridiculously complex. Codename One tries to simplify this and also make it portable. Sometimes complexity leaks out and the nuances can be difficult to deal with. -Simply explained an application has three states: +Explained an application has three states: * *Foreground* - it's running and in the foreground which means the user can physically interact with the app * *Suspended* - the app isn't in the foreground, it's either paused or has a background process running * *Not Running* - the app was never launched, was killed or crashed -The lifecycle is the process of transitioning between these 3 states and the callbacks invoked when such a transition occurs. The first time we launch the app we start from a "`Cold Start`" (Not Running State) but on subsequent launches the app is usually started from the "Warm Start" (Suspended State). +The lifecycle is the process of transitioning between these 3 states and the callbacks invoked when such a transition occurs. The first time you launch the app you start from a "`Cold Start`" (Not Running State) but on later launches the app is started from the "Warm Start" (Suspended State). .Codename One Application Lifecycle image::img/codenameone-application-lifecycle.png[Codename One Application Lifecycle] @@ -413,8 +413,8 @@ Codename One has four standard callback methods in the lifecycle API: * `start()` - is invoked for two separate cases. After `start()` is finished the app transitions to the _Foreground_ state. ** Following `init(Object)` in case of a cold start. Cold start refers to starting the app from a _Not Running_ state. ** When the app is restored from _Suspended_ state. In this case `init(Object)` isn't invoked -* `stop()` - is invoked when the app is minimized e.g. when switching to a different app. After `stop()` is finished the app transitions to the _Suspended_ state. -* `destroy()` - is invoked when the app is destroyed e.g. killed by a user in the task manager. After `destroy()` is finished the app is no longer running hence it's in the _Not Running_ state. +* `stop()` - is invoked when the app is minimized for example, when switching to a different app. After `stop()` is finished the app transitions to the _Suspended_ state. +* `destroy()` - is invoked when the app is destroyed for example, killed by a user in the task manager. After `destroy()` is finished the app is no longer running hence it's in the _Not Running_ state. IMPORTANT: `destroy()` is optional there is no guarantee that it will be invoked. It should be used only as a last resort **** @@ -445,14 +445,14 @@ public void destroy() { // <4> that's it. Hopefully you've a general sense of the code. it's time to run on the device. -==== Building and Deploying On Devices +==== Building and deploying on devices .Codename One Control Center image::img/control-center-main.png[Codename One Settings/Control Center,scaledwidth=50%] You can use the Control Center to configure almost anything. Specifically, the application title, application version, application icon etc. are all found in the Codename One Settings maven target. -There are many options within this UI that control almost every aspect of the application from signing to basic settings. +many options within this UI that control almost every aspect of the application from signing to basic settings. Your device builds using the Codename One Cloud can also be found right here as well as subscription information. @@ -461,7 +461,7 @@ image::img/control-center-builds-empty.png[Builds in Empty State,scaledwidth=50% ===== Signing/Certificates -All of the modern mobile platforms require signed applications but they all take radically different approaches when implementing it. +All the modern mobile platforms require signed applications but they all take radically different approaches when implementing it. Signing is a process that marks your final application for the device with a special value. This value (signature) is a value that you can generate based on the content of the application and your certificate. Effectively it guarantees the app came from you. This blocks a 3rd party from signing their apps and posing as you to the App Store or to the user. it's a crucial security layer. @@ -476,7 +476,7 @@ Android uses a self signed certificate approach. You can generate a certificate Anyone can do that. For example, once a certificate is published it can't be replaced... -If this was not the case someone else could potentially push an "`upgrade`" to your app. Once an app is submitted with a certificate to Google Play this app can't be updated with any other certificate. +If this wasn't the case someone else could push an "`upgrade`" to your app. Once an app is submitted with a certificate to Google Play this app can't be updated with any other certificate. With that in mind generating an Android certificate is trivial. @@ -490,23 +490,23 @@ TIP: Make sure to back that up and the password as losing these can have dire co .Should I Use a Different Certificate for Each App? **** -In theory yes. In practice it's a pain... Keeping multiple certificates and managing them is a pain so we often just use one. +In theory yes. In practice it's a pain... Keeping multiple certificates and managing them is a pain so you often just use one. -The drawback of this approach occurs when you're building an app for someone else or want to sell the app. Giving away your certificate is akin to giving away your house keys. So it makes sense to have separate certificates for each app. +The drawback of this approach occurs when you're building an app for someone else or want to sell the app. Giving away your certificate is akin to giving away your house keys. it makes sense to have separate certificates for each app. **** -====== Signing and Provisioning iOS +====== Signing and provisioning iOS -Code signing for iOS relies on Apple as the certificate authority. This is something that doesn't exist on Android. iOS also requires provisioning as part of the certificate process and completely separates the process for development/release. +Code signing for iOS relies on Apple as the certificate authority. This is something that doesn't exist on Android. iOS also requires provisioning as part of the certificate process and separates the process for development/release. -But first let's start with the good news: +However, first let's start with the good news: * Losing an iOS certificate is no big deal - in fact you revoke them often with no impact on shipping apps * Codename One has a wizard that hides most of the pain related to iOS signing In iOS Apple issues the certificates for your applications. That way the certificate is trusted by Apple and is assigned to your Apple iOS developer account. There is one important caveat: You need an iOS Developer Account and Apple charges a 99USD Annual fee for that. -TIP: The 99USD price and requirement have been around since the introduction of the iOS developer program for roughly 10 years at the time of this writing. It might change at some point though +TIP: The 99USD price and need have been around since the introduction of the iOS developer program for 10 years at the time of this writing. It might change at some point though Apple also requires a "`provisioning profile`" which is a special file bound to your certificate and app. This file describes some details about the app to the iOS installation process. One of the details it includes during development is the list of permitted devices. @@ -515,9 +515,9 @@ image::img/ios-certificates-provisioning.png[The 4 files Required for iOS Signin You need 4 files for signing. Two certificates and two provisioning profiles: -. *Production* -- The production certificate/provisioning pair is used for builds that are uploaded to iTunes +. *Production:* The production certificate/provisioning pair is used for builds that are uploaded to iTunes -. *Development* -- The development certificate/provisioning is used to install on your development devices +. *Development:* The development certificate/provisioning is used to install on your development devices The certificate wizard automatically creates these 4 files and configures them for you. @@ -548,11 +548,11 @@ The simplest and most reliable process for getting a UDID is through iTunes. I h NOTE: Ad hoc provisioning allows 1000 beta testers for your application but it's a more complex process that you won't discuss here although it's supported by Codename One -===== Build and Install +===== Build and install -Before you continue with the build you should sign up at https://www.codenameone.com/build-server.html where you can soon follow the progress of your builds. You need a Codename One account in order to build for the device. +Before you continue with the build you should sign up at https://www.codenameone.com/build-server.html where you can soon follow the progress of your builds. You need a Codename One account to build for the device. -Now that you've certificates the process of device builds is literally a right click away for both OS's. You can right click the project and select #Codename One# -> #Send iOS Debug Build# or #Codename One# -> #Send Android Build#. +Now that you've certificates the process of device builds is a right click away for both OS's. You can right click the project and select #Codename One# -> #Send iOS Debug Build# or #Codename One# -> #Send Android Build#. .Right click menu options for sending device builds image::img/getting-started-right-click-menu.png[Right click menu options for sending device builds,scaledwidth=50%] @@ -571,12 +571,12 @@ Once you go through those steps you should have the #HelloWorld# app running on TIP: You can also install the application either by emailing the install link to your account (using the #e-mail Link# button) -You can also download the binaries in order to upload them to the appstores. +You can also download the binaries to upload them to the appstores. === Kotlin -Codename One started before Kotlin became public. Kotlin has since shown itself as an interesting option for developers especially within the Android community. With that in mind you decided to integrate support for Kotlin into Codename One. +Codename One started before Kotlin became public. Kotlin has since shown itself as an interesting option for developers within the Android community. With that in mind, Codename One added support for Kotlin. To use Kotlin with Codename One you can create a kotlin directory next to the java directory under the `common/src/main` directory. Kotlin code that resides there can work as usual and interact with the Java code. @@ -584,11 +584,11 @@ Please notice the following: - don't use the project conversion tools or accept the warning that the project isn't a Kotlin project. You do your own build process -- Warnings and errors aren't listed correctly and builds that claim to have errors might pass +- Warnings and errors aren't listed and builds that claim to have errors might pass ==== Hello Kotlin -Due to the way Kotlin works you can create a regular Java project and convert sources to Kotlin. You can mix Java and Kotlin code without a problem and Codename One would "work". +Due to the way Kotlin works you can create a regular Java project and convert sources to Kotlin. You can mix Java and Kotlin code without a problem and Codename One would "work." The hello world Java source file looks like this (removed some comments and whitespace): @@ -677,9 +677,9 @@ This is essential as the build server will fail with weird errors related to ins NOTE: This applies to the main class of the project, other classes in Codename One can remain `final` -The second problem is that arguments are non-null by default. The `init` method might have a null argument. So this fails with an exception. The solution is to add a question mark to the end of the call: `fun init(context: Any?)`. +The second problem is that arguments are non-null by default. The `init` method might have a null argument. this fails with an exception. The solution is to add a question mark to the end of the call: `fun init(context: Any?)`. -So the full working sample is: +the full working sample is: [source,kotlin] ---- @@ -715,4 +715,4 @@ open class MyApplication { } ---- -Once all of that's in place Kotlin should work. This should be possible for additional JVM languages in the future. +Once all that's in place Kotlin should work. This should be possible for more JVM languages in the future. diff --git a/docs/developer-guide/Maven-Appendix-API.adoc b/docs/developer-guide/Maven-Appendix-API.adoc index 47f1e7bad5..36b521c7b2 100644 --- a/docs/developer-guide/Maven-Appendix-API.adoc +++ b/docs/developer-guide/Maven-Appendix-API.adoc @@ -17,13 +17,13 @@ Codename One is much more than an API library. It provides a full tool-chain and No reflection:: Codename One apps don't support reflection because reflection makes it impossible to keep app-size down. -=== Kotlin API Support +=== Kotlin API support -Codename One apps support the https://kotlinlang.org/api/latest/jvm/stdlib/[Kotlin Standard Library]. Kotlin support is still relatively new, so are still discovering which libraries are compatible. APIs that rely on reflection won't be supported, but most other libraries should work. +Codename One apps support the https://kotlinlang.org/api/latest/jvm/stdlib/[Kotlin Standard Library]. Kotlin support is still new, so are still discovering which libraries are compatible. APIs that rely on reflection won't be supported, but most other libraries should work. -=== Add-on Libraries +=== Add-on libraries -Add-on libraries can be added to your library in the common/pom.xml file, however, if you use APIs that aren't supported by Codename One (e.g. which use reflection), then your app will fail to build. +Add-on libraries can be added to your library in the common/pom.xml file, but, if you use APIs that aren't supported by Codename One (for example, which use reflection), then your app will fail to build. Codename One supports its own library format (cn1lib) which sort of "certifies" that it's compatible with Codename One. You can browse the growing catalog of available cn1libs inside <>. diff --git a/docs/developer-guide/Maven-Appendix-Archetypes.adoc b/docs/developer-guide/Maven-Appendix-Archetypes.adoc index 8db1a192b8..fd4bbaf112 100644 --- a/docs/developer-guide/Maven-Appendix-Archetypes.adoc +++ b/docs/developer-guide/Maven-Appendix-Archetypes.adoc @@ -1,8 +1,8 @@ [appendix] -== Project Archetypes +== Project archetypes [#cn1app-archetype] -=== Codename One Application Project Archetype (cn1app-archetype) +=== Codename One application project archetype (cn1app-archetype) The `cn1app-archetype` is the basis for all maven Codename One application projects. It provides a multimodule project with the following modules: @@ -11,7 +11,7 @@ See https://shannah.github.io/cn1-maven-archetypes/cn1app-archetype-tutorial/get Also see <> for an example using this archetype from the command-line to create a bare-bones Java project. [#cn1lib-archetype] -=== Codename One Library Project Archetype (cn1lib-archetype) +=== Codename One library project archetype (cn1lib-archetype) The `cn1lib-archetype` provides a project for building Codename One libraries. See <> for details on using this archetype. diff --git a/docs/developer-guide/Maven-Appendix-Control-Center.adoc b/docs/developer-guide/Maven-Appendix-Control-Center.adoc index d9d020efd0..eb1fa7db2b 100644 --- a/docs/developer-guide/Maven-Appendix-Control-Center.adoc +++ b/docs/developer-guide/Maven-Appendix-Control-Center.adoc @@ -1,13 +1,13 @@ [appendix] [#settings] -== Codename One Settings +== Codename One settings The Codename One Settings app (aka Codename One Preferences, aka Control Center) allows you to configure many aspects of your application. This is where you can generate certificates, browse/install add-ons, monitor the status of your cloud builds, configure build hints, and more. -=== Opening Codename One Settings +=== Opening Codename One settings -==== Opening Codename One Settings from Command-line +==== Opening Codename One settings from command-line Use the `run.sh` (or run.bat, if on Windows) to open Codename One settings: @@ -16,27 +16,27 @@ Use the `run.sh` (or run.bat, if on Windows) to open Codename One settings: ./run.sh settings ---- -==== Opening Codename One Settings from IntelliJ +==== Opening Codename One settings from IntelliJ Click on the "Configuration" menu in the upper right of the toolbar, and select "Tools" > "Codename One Settings" as shown below. image::img/intellij-open-settings.png[] -==== Opening Codename One Settings from NetBeans +==== Opening Codename One settings from NetBeans Right-click on the project in the project inspector, and select "Maven" > "Open Control Center" as shown below: image::img/netbeans-open-control-center.png[] -==== Opening Codename One Settings from Eclipse +==== Opening Codename One settings from Eclipse Press the image:img/eclipse-run-as-button.png[] button, and select the "_My Project_ Settings" option. (Where _My Project_ is the name of your project). E.g. image:img/eclipse-open-settings.png[] [#dashboard] -=== The Dashboard +=== The dashboard Once inside Codename One Settings, you'll see a dashboard like the following: diff --git a/docs/developer-guide/Maven-Appendix-Goals.adoc b/docs/developer-guide/Maven-Appendix-Goals.adoc index 220d5cf157..5333ea6be0 100644 --- a/docs/developer-guide/Maven-Appendix-Goals.adoc +++ b/docs/developer-guide/Maven-Appendix-Goals.adoc @@ -1,5 +1,5 @@ [appendix] -== Maven Goals +== Maven goals include::appendix_goal_build.adoc[] diff --git a/docs/developer-guide/Maven-Appendix-Rich-Properties.adoc b/docs/developer-guide/Maven-Appendix-Rich-Properties.adoc index aed3d3ba58..e79af564e4 100644 --- a/docs/developer-guide/Maven-Appendix-Rich-Properties.adoc +++ b/docs/developer-guide/Maven-Appendix-Rich-Properties.adoc @@ -1,6 +1,6 @@ [#rich-properties-file] [appendix] -== Rich Properties File (rpf) Format +== Rich properties file (rpf) format The rich properties file (rpf) format is used to store configuration for the `generate-app-project` goal. The format is the same as a regular properties file except that it can more easily accommodate properties whose values are "rich" and lengthy. @@ -18,7 +18,7 @@ Key value <3> <3> The property value, which can be one or more lines of content. <4> The separator with the same number of equals signs on a line of its own. -=== Example Rich Properties File +=== Example rich properties file [source,rpf] ---- diff --git a/docs/developer-guide/Maven-Creating-CN1Libs.adoc b/docs/developer-guide/Maven-Creating-CN1Libs.adoc index b805c5a6e8..f4cd28a393 100644 --- a/docs/developer-guide/Maven-Creating-CN1Libs.adoc +++ b/docs/developer-guide/Maven-Creating-CN1Libs.adoc @@ -1,5 +1,5 @@ [#creating-cn1libs] -== Codename One Libraries +== Codename One libraries A Codename One Library (cn1lib) is a module that can be distributed and added to Codename One applications to add functionality. It can be distributed as a self-contained bundle (a file with the.cn1lib extension), or deployed on Maven central to be included in application projects as a pom dependency. @@ -13,23 +13,24 @@ A cn1lib may contain any of the following: ..cn1lib vs.jar [sidebar] **** -You may be wondering why the .cn1lib format is even necessary. Why not just distribute libraries as .jar files? The .cn1lib format offers several advantages over the plain .jar format: +You may be wondering why the .cn1lib format is even necessary. Why not just distribute libraries as .jar files? The .cn1lib format offers several advantages over the plain .jar format: -. *cn1libs can contain platform-specific native sources* that make use of native APIs on the various platforms. E.g. they can contain Objective-C code which will be compiled on the build server when deploying on iOS. -. *Codename One library projects perform a compliance check* at the time that the library is compiled to ensure it only uses supported Codename One APIs. This provides a sort of "certification" that the library will be compatible with Codename One application projects. +. *cn1libs can contain platform-specific native sources* that make use of native APIs on the various platforms. For example, they can contain Objective-C code which will be compiled on the build server when deploying on iOS. +. *Codename One library projects perform a compliance check* at the time that the library is compiled to ensure it only uses supported Codename One APIs. This provides a sort of "certification" that the library will be compatible with Codename One application projects. . *Codename One libraries can include CSS files and build hints* which will be appended to the build hints of application projects when they are built. -All that said, you *can* still distribute libraries as plain old jars and include them in your Maven Codename One projects as *jar* dependencies. Codename One application projects will perform an additional compliance check to ensure that the jar is compatible, and the build will fail if it uses APIs that aren't available in Codename One. +// vale-skip: Microsoft.ComplexWords/TooWordy — 'an additional compliance check' reads correctly; 'an more compliance check' is ungrammatical. +All that said, you *can* still distribute libraries as plain old jars and include them in your Maven Codename One projects as *jar* dependencies. Codename One application projects will perform an additional compliance check to ensure that the jar is compatible, and the build will fail if it uses APIs that aren't available in Codename One. -TIP: Codename One supports a subset of JavaSE8, as well as addition APIs for accessing device functionality and building beautiful user interfaces. See https://www.codenameone.com/javadoc/[the Codename One javadocs] for a definitive list of supported APIs. +TIP: Codename One supports a subset of JavaSE8, as well as addition APIs for accessing device functionality and building beautiful user interfaces. See https://www.codenameone.com/javadoc/[the Codename One javadocs] for a definitive list of supported APIs. **** -=== Creating a Library Project +=== Creating a library project Use the `cn1lib-archetype` for generating a new Codename One library project as follows: -==== Command Line +==== Command line [source,bash] ---- @@ -43,13 +44,13 @@ mvn archetype:generate \ -DinteractiveMode=false ---- -NOTE: This command is formatted for the bash prompt (e.g. Linux or Mac). It will work on Windows also if you use bash. If you're on Windows and are using PowerShell or the regular command prompt, then you'll need to modiy the command slightly. In particular, the entire command would need to be on a single line. (Remove the '\' at the end of each line, and merge lines together, with space between the command-line flags) +NOTE: This command is formatted for the bash prompt (for example, Linux or Mac). It will work on Windows also if you use bash. If you're on Windows and are using PowerShell or the regular command prompt, then you'll need to modiy the command slightly. In particular, the entire command would need to be on a single line. (Remove the '\' at the end of each line, and merge lines together, with space between the command-line flags) In the above snippet you would change the `groupId`, `artifactId`, and `version` properties to reflect your project settings. [TIP] ==== -You can run the `archetype:generate` goal with as many or few properties as you like, and it will prompt you to enter any properties that are required. E.g. You could enter: +You can run the `archetype:generate` goal with as many or few properties as you like, and it will prompt you to enter any properties that are required. For example, You could enter: [source,bash] ---- @@ -64,7 +65,7 @@ mvn archetype:generate -DarchetypeGroupId=com.codenameone \ -DarchetypeArtifactId=cn1lib-archetype ---- -NOTE: This command is formatted for the bash prompt (e.g. Linux or Mac). It will work on Windows also if you use bash. If you're on Windows and are using PowerShell or the regular command prompt, then you'll need to modiy the command slightly. In particular, the entire command would need to be on a single line. (Remove the '\' at the end of each line, and merge lines together, with space between the command-line flags) +NOTE: This command is formatted for the bash prompt (for example, Linux or Mac). It will work on Windows also if you use bash. If you're on Windows and are using PowerShell or the regular command prompt, then you'll need to modiy the command slightly. In particular, the entire command would need to be on a single line. (Remove the '\' at the end of each line, and merge lines together, with space between the command-line flags) And follow the prompts. This will, result in fewer prompts because you've already specified the archetype to use. ==== @@ -80,13 +81,13 @@ image::img/intellij-new-project-menu.png[] + image::img/intellij-new-project-dialog-maven.png[] . Check the "Create from Archetype" checkbox. image:img/intellij-create-from-archetype-checkbox.png[]. This should allow you to choose from of archetypes that are already known to IntelliJ. -. If you don't see an option for "com.codenameone:cn1lib-archetype", then IntelliJ doesn't know about it yet. If, however you *do* see this option, you can skip to the next step. Press the "Add Archetype..." button. This will display a dialog for you to enter the details of the archetype. +. If you don't see an option for `com.codenameone:cn1lib-archetype`, then IntelliJ doesn't know about it yet. If, but you *do* see this option, you can skip to the next step. Press the "Add Archetype..." button. This will display a dialog for you to enter the details of the archetype. + image::img/intellij-add-archetype.png[] + -Fill in this dialog as shown in the above image. Specifically `groupId`="com.codenameone", `artifactId`="cn1lib-archetype", and `version`="LATEST" +Fill in this dialog as shown in the above image. Specifically `groupId`=`com.codenameone`, `artifactId`="cn1lib-archetype," and `version`="LATEST" + -Then press "OK". +Then press `OK`. . Select the option that says "com.codenameone:cn1lib-archetype" + image::img/intellij-select-cn1lib-archetype.png[] @@ -97,7 +98,7 @@ Then press "Next" image::img/intellij-new-cn1lib-project-details-form.png[] + Then click "Next" -. The final form in this wizard summarizes the project details and gives you an opportunity to add additional properties to pass to the `archetype:generate` goal. In our case we don't need to add any additional properties. If the information looks correct, you can press "Next". +. The final form in this wizard summarizes the project details and gives you an opportunity to add more properties to pass to the `archetype:generate` goal. In your case you don't need to add any more properties. If the information looks correct, you can press `Next`. + image::img/intellij-new-cn1lib-project-details-summary.png[] @@ -153,22 +154,22 @@ image:img/eclipse-new-maven-project-details.png[] This will create a new libary project for you at the location you specified. -==== Project Structure +==== Project structure -Let us take a look at the project that was created. it's a multi-module Maven project with the following modules: +Let's take a look at the project that was created. it's a multi-module Maven project with the following modules: common:: -The module where you'll add all of your cross-platform code and CSS, and build hint configuration. This module is in the "common" directory of the main project. +The module where you'll add all your cross-platform code and CSS, and build hint configuration. This module is in the "common" directory of the main project. Java SE:: The module where you can implement native interfaces for the Java SE platform. This module is in the "Java SE" directory of the main project. ios:: The module where you can implement native interfaces for the iOS platform. This module is in the "ios" directory of the main project. android:: The module where you can implement native interfaces for the Android platform. This module is in the "android" directory of the main project. -javascript:: -The module where you can implement native interfaces for the Javascript platform. This module is in the "javascript" directory of the main project. +JavaScript:: +The module where you can implement native interfaces for the Javascript platform. This module is in the "JavaScript" directory of the main project. lib:: -The library module which includes all of the other modules as dependencies, and can be used as a pom dependency in Codename One application projects that wish to use this library. This module is in the "lib" directory of the main project. +The library module which includes all the other modules as dependencies, and can be used as a pom dependency in Codename One application projects that wish to use this library. This module is in the "lib" directory of the main project. tests:: An application project for writing unit tests against your library. This module is in the "tests" directory of the main project. @@ -179,7 +180,7 @@ The project inspector will look like: image::img/intellij-myfirstlib-project-inspector.png[] -This top-level view of the module structure may seem daunting. Most of your development will occur inside the "common" module. If we expand that module it will look more familiar to developers who have used the old Ant project structure: +This top-level view of the module structure may seem daunting. Most of your development will occur inside the "common" module. If you expand that module it will look more familiar to developers who have used the old Ant project structure: image::img/intellij-myfirstlibrary-common-project-files.png[] @@ -191,7 +192,7 @@ The project inspector will look like: image::img/netbeans-myfirstlibrary-project-inspector.png[] -This top-level view of the modules doesn't provide a clear view of the project landscape, however, since 99% of your development will occur inside the `common` submodule. Let us open that "common" sub-module project as well and take a peek. +This top-level view of the modules doesn't provide a clear view of the project landscape, but, since 99% of your development will occur inside the `common` submodule. Let's open that "common" sub-module project as well and take a peek. Right click on the "Common" sub-module, and select "Open Project" as shown below: @@ -203,7 +204,7 @@ image::img/netbeans-myfirstlibrary-project-inspector-with-common.png[] In this screenshot I have expanded "Source Packages" and "Other Sources/css" to highlight where your Java source files and CSS source files will be located. -The project inspector hides a few important files, however, so here is a screenshot of the File inspector for the common project: +The project inspector hides a few important files, but, so here is a screenshot of the File inspector for the common project: image::img/netbeans-my-first-library-file-inspector-common.png[] @@ -215,9 +216,9 @@ image::img/eclipse-cn1lib-package-explorer.png[] In this screenshot, I have expanded the _common/src/main/css_ and _common/src/main/java_ directories as this is where most of your module source will go. -===== Command Line +===== Command line -If we do a file listing on the project directory, it shows the following: +If you do a file listing on the project directory, it shows the following: [source,listing] ---- @@ -309,27 +310,27 @@ Steves-Mac-Pro:MyFirstLibrary shannah$ find . This may seem daunting at first, but it's important to realize that 99% of the time, you'll be working in the "common" module - most of the other stuff is boilerplate. -===== Important Files +===== Important files -There are a few key files in this project that you'll be using more than the others. +a few key files in this project that you'll be using more than the others. pom.xml:: The maven configuration file of the root module is where you will set project-wide properties such as the `cn1.version` property, which specifies the version of the Codename One libraries that the module should be compiled against. Periodically, you'll want to update the `cn1.version` property to point to the latest version. + -When/if you decide to deploy your module to Maven central, you'll need to add additional deployment-related settings in this file. +When/if you decide to deploy your module to Maven central, you'll need to add more deployment-related settings in this file. common/pom.xml:: -The maven configuration file for the "common" module, which will contain most of your cn1lib's soure code, CSS files, and properties files. If your library depends on other libraries or jar files, you'll usually be adding them as dependencies in this file, and not the root pom.xml file. +The maven configuration file for the "common" module, which will contain most of your cn1lib's soure code, CSS files, and properties files. If your library depends on other libraries or jar files, you'll be adding them as dependencies in this file, and not the root pom.xml file. common/codenameone_library_appended.properties:: -This file is where you can specify properties that should be merged with the codenameone_settings.properties of application projects that include this library as a dependency. This is where you would add, for example, Gradle dependencies required for the Android builds, or cocoapods dependencies that are required for iOS builds. +This file is where you can specify properties that should be merged with the codenameone_settings.properties of application projects that include this library as a dependency. This is where you would add, for example, Gradle dependencies required for the Android builds, or CocoaPods dependencies that are required for iOS builds. common/codenameone_library_required.properties:: This file allows you to specific build hints that *must* be present in application projects that include this library. If this libary requires a particular android build tools version, or a specific Java version, then those requirements should be specified in this file. -===== Important Directories +===== Important directories -As mentioned previously, 99% of all of your development will likely occur inside the "common" module. The other modules are mostly for native implementations of Native interfaces. +As mentioned earlier, 99% of all your development will likely occur inside the "common" module. The other modules are for native implementations of Native interfaces. common/src/main/java:: This is where your cross-platform Java source files will be placed. @@ -341,11 +342,11 @@ common/src/main/resources:: Other non-java resources that you want to have included in the classpath. [#building-library] -==== Building the Library +==== Building the library -===== Command Line +===== Command line -To build the library, simply run the "install" goal on the root module as follows: +To build the library, run the "install" goal on the root module as follows: [source,bash] ---- @@ -358,13 +359,13 @@ Press the "build" image:img/intellij-build-icon.png[] button on the toolbar. ===== NetBeans -Right click on the "root" module in the project explorer and select "Build". +Right click on the "root" module in the project explorer and select `Build`. image::img/netbeans-right-click-build.png[] IMPORTANT: You must build the root module and not one of the submodules. -Alternatively you could have selected the "root" module in the project explorer and pressed the "build" image:img/netbeans-build-button.png[] button on the toolbar. +Or you could have selected the "root" module in the project explorer and pressed the "build" image:img/netbeans-build-button.png[] button on the toolbar. ===== Eclipse IDE IDE @@ -375,17 +376,17 @@ image::img/eclipse-build-cn1lib.png[] TIP: If the build fails for any reason, check to make sure that your project is using the latest version of the Codename One plugin. You can do this by opening the _pom.xml_ file, and changing the `cn1.version` and `cn1.plugin.version` properties to reference the latest version. Check for the latest version https://search.maven.org/artifact/com.codenameone/codenameone[here]. -===== Building the Legacy.cnlib File +===== Building the Legacy.cnlib file -When using the Maven build tool, we no longer require the.cn1lib file at all. Your library projects can be handled entirely via Maven's dependency mechanism. The preferred way to distribute your libraries is on Maven central, and the preferred way to add a library to an application is via a Maven "pom" dependency. +When using the Maven build tool, you no longer require the.cn1lib file at all. Your library projects can be handled entirely via Maven's dependency mechanism. The preferred way to distribute your libraries is on Maven central, and the preferred way to add a library to an application is via a Maven "pom" dependency. That being said, you may still want to distribute your library as a.cn1lib file for the sake of users who are still using Ant as their build tool. For that reason, when you bulid a library project, the cn1lib is automatically built as well. After running a build, you can look in the common/target directory and find your.cn1lib file ready to be distributed. ==== Editing Java Code -In order to get acquainted with our project, let's add a "Hello World" java class that we want to make available as part of our cn1lib. +To get acquainted with your project, let's add a "Hello World" Java class that you want to make available as part of your cn1lib. -Add a new class inside the "common/src/main/java" directory with package "com.example", and name "HelloWorld". Enter the following contents into the class: +Add a new class inside the "common/src/main/java" directory with package `com.example`, and name `HelloWorld`. Enter the following contents into the class: [source,java] ---- @@ -400,9 +401,9 @@ public class HelloWorld { Now build the library again. (See <>). -==== Using the Library in an Application Project +==== Using the library in an application project -Now that you've built our library and added a Java class, let's try adding it as a dependency in an application project. If you haven't yet created an application project, do that now. See <> for instructions on creating a new application project. +Now that you've built your library and added a Java class, let's try adding it as a dependency in an application project. If you haven't yet created an application project, do that now. See <> for instructions on creating a new application project. Open the common/pom.xml file of your application project. @@ -420,7 +421,7 @@ The common/pom.xml file will have more than one `` tag, as it incl You should add your dependencies before this comment. -For the sake of this example, suppose our library was set up with the following coordinates: +For the sake of this example, suppose your library was set up with the following coordinates: |==== | *groupId:* | `com.example` @@ -428,7 +429,7 @@ For the sake of this example, suppose our library was set up with the following | *version:* | `1.0-SNAPSHOT` |==== -In this case we would add the following XML snippet to the `` section of our application's common/pom.xml file: +In this case you would add the following XML snippet to the `` section of your application's common/pom.xml file: [source,xml] ---- @@ -440,7 +441,7 @@ In this case we would add the following XML snippet to the `` sect ---- -IMPORTANT: Notice that we appended "-lib" to the `artifactId`. This is because you're including the "lib" module of our library project as the dependency, and not the root module. Also the `pom` is important as it indicates that this is a pom dependency - not a regular jar dependency. +IMPORTANT: Notice that you appended "-lib" to the `artifactId`. This is because you're including the "lib" module of your library project as the dependency, and not the root module. Also the `pom` is important as it indicates that this is a pom dependency - not a regular jar dependency. Now let's try it out. Try adding the following code to your application project's main class (or anywhere in the application project, for that matter): @@ -451,7 +452,7 @@ com.example.HelloWorld.helloWorld(); And build the project. The project should build OK, and if you run it, you should see that the `helloWorld()` method works as designed. -=== Distributing Your Library +=== Distributing your library The recommended way to distribute your library is on Maven central. That way users will be able to install your library by copying and pasting a familiar `` snippet into their pom.xml file. diff --git a/docs/developer-guide/Maven-Getting-Started.adoc b/docs/developer-guide/Maven-Getting-Started.adoc index fe821252d2..45747bc260 100644 --- a/docs/developer-guide/Maven-Getting-Started.adoc +++ b/docs/developer-guide/Maven-Getting-Started.adoc @@ -1,9 +1,9 @@ -== Getting Started +== Getting started [#creating-app-project] -=== Creating a New Project +=== Creating a new project -==== Codename One Initializr +==== Codename One initializr The easiest way to create a new project is to use the https://start.codenameone.com[Codename One initializr]. @@ -27,10 +27,10 @@ See https://shannah.github.io/cn1app-archetype-kotlin-template/getting-started.h ==== -==== Generating a New Project from the Command-line +==== Generating a new project from the command-line [#cn1app-archetype-example] -===== Bare-bones Java Project +===== Bare-bones Java project If you prefer to generate your projects directly on the command-line, you can use the <> to generate the project directly on the command-line. @@ -47,7 +47,7 @@ mvn archetype:generate \ -DinteractiveMode=false ---- -This will generate a project in the current directory. The project's directory will have the same name as the artifact ID you specified here. E.g. If your command had `-DartifactId=myapp`, then the project will be located in a newly created directory named "myapp". +This will generate a project in the current directory. The project's directory will have the same name as the artifact ID you specified here. For example, If your command had `-DartifactId=myapp`, then the project will be located in a newly created directory named "myapp." NOTE: If you haven't used Maven archetypes before, this snippet may be confusing. See https://maven.apache.org/guides/introduction/introduction-to-archetypes.html[Introduction to Maven Archetypes] to get up to speed. @@ -66,13 +66,13 @@ This command uses the <<#cn1app-archetype>> which has the following Maven coordi This archetype generates a bare-bones Java project (the same one described in https://shannah.github.io/cn1-maven-archetypes/cn1app-archetype-tutorial/getting-started.html[Getting Started with the Bare-bones Java App Template]). -TIP: View the source of the the cn1app-archetype project https://github.com/shannah/cn1-maven-archetypes/tree/master/cn1app-archetype[here]. +TIP: View the source of the cn1app-archetype project https://github.com/shannah/cn1-maven-archetypes/tree/master/cn1app-archetype[here]. You can learn more about using the archetype in <>. -===== Project Templates +===== Project templates -The https://shannah.github.io/cn1app-archetype-kotlin-template/getting-started.html[bare-bones Kotlin App project template] is an alternative starter project that uses Kotlin as the primary language instead of Java. it's built on the <> at its core, but it includes some additional configuration settings and sources to modify the template. You can use such templates as starter projects by using the `generate-app-project` goal of the Codename One Maven plugin. +The https://shannah.github.io/cn1app-archetype-kotlin-template/getting-started.html[bare-bones Kotlin App project template] is an alternative starter project that uses Kotlin as the primary language instead of Java. it's built on the <> at its core, but it includes some more configuration settings and sources to change the template. You can use such templates as starter projects by using the `generate-app-project` goal of the Codename One Maven plugin. Here is an example which generates a project based on the bare-bones kotlin template: @@ -90,16 +90,16 @@ mvn com.codenameone:codenameone-maven-plugin:{cn1-plugin-release-version}:genera -DsourceProject=/path/to/kotlin-example-app ---- -NOTE: This command is formatted for the bash prompt (e.g. Linux or Mac). It will work on Windows also if you use bash. If you're on Windows and are using PowerShell or the regular command prompt, then you'll need to modiy the command slightly. In particular, the entire command would need to be on a single line. (Remove the '\' at the end of each line, and merge lines together, with space between the command-line flags) +NOTE: This command is formatted for the bash prompt (for example, Linux or Mac). It will work on Windows also if you use bash. If you're on Windows and are using PowerShell or the regular command prompt, then you'll need to modiy the command slightly. In particular, the entire command would need to be on a single line. (Remove the '\' at the end of each line, and merge lines together, with space between the command-line flags) -Like the `archetype:generate` goal, this will create the project in a directory named after your specified artifact ID. E.g. If your command included `-DartifactId=myapp`, then the project would be in a newly-created directory named "myapp". +Like the `archetype:generate` goal, this will create the project in a directory named after your specified artifact ID. For example, If your command included `-DartifactId=myapp`, then the project would be in a newly created directory named "myapp." Some notes here: -. The `com.codenameone:codenameone-maven-plugin:{cn1-plugin-release-version}:generate-app-project` argument is the fully-qualified goal name for the `generate-app-project`. This is necessary since we aren't running this goal in the context of any existing project. You should adjust the version number (`{cn1-plugin-release-version}`) to reflect the https://search.maven.org/search?q=a:codenameone-maven-plugin[latest available Codename One version on Maven Central]. +. The `com.codenameone:codenameone-maven-plugin:{cn1-plugin-release-version}:generate-app-project` argument is the fully qualified goal name for the `generate-app-project`. This is necessary since you aren't running this goal in the context of any existing project. You should adjust the version number (`{cn1-plugin-release-version}`) to reflect the https://search.maven.org/search?q=a:codenameone-maven-plugin[latest available Codename One version on Maven Central]. . The `archetypeGroupId`, `archetypeArtifactId`, and `archetypeVersion` parameters are the same as when using the `archetype:generate` goal, and they will (almost) always refer to the <>. . The `groupId`, `artifactId`, and `version` work the same as for the `archetype:generate` goal. that's, that they specify the coordinates for your newly created project. -. The `mainName` specifies the Main class name for your app. This is the class name, and should not include the full package. E.g. "MyApp", not "com.example.MyApp" +. The `mainName` specifies the Main class name for your app. This is the class name, and shouldn't include the full package. For example, `MyApp`, not "com.example.MyApp" . The `sourceProject` property is the path to the "template" project. In this case, you will assume that you've cloned the https://github.com/shannah/cn1app-archetype-kotlin-template[bare-bones kotlin project template repository] at /path/to/kotlin-example-app. [TIP] @@ -110,9 +110,9 @@ See <> for instructions on building your own project ==== [#migrate-existing-project] -=== Migrating an Existing Project +=== Migrating an existing project -If you've an existing Codename One application project that uses the old Ant project structure, you can use the `generate-app-project` goal to migrate the project over to maven. This goal doesn't make any changes to the Ant project. It creates a new Maven project and copies over all of the project sources and libraries, reorganized to fit the new project structure. +If you've an existing Codename One application project that uses the old Ant project structure, you can use the `generate-app-project` goal to migrate the project over to maven. This goal doesn't make any changes to the Ant project. It creates a new Maven project and copies over all the project sources and libraries, reorganized to fit the new project structure. A minimal invocation of this goal would look like: @@ -129,13 +129,13 @@ mvn com.codenameone:codenameone-maven-plugin:$CN1VERSION:generate-app-project \ -Dcn1Version=$CN1VERSION ---- -NOTE: This command is formatted for the bash prompt (e.g. Linux or Mac). It will work on Windows also if you use bash. If you're on Windows and are using PowerShell or the regular command prompt, then you'll need to modiy the command slightly. In particular, the entire command would need to be on a single line. (Remove the '\' at the end of each line, and merge lines together, with space between the command-line flags) +NOTE: This command is formatted for the bash prompt (for example, Linux or Mac). It will work on Windows also if you use bash. If you're on Windows and are using PowerShell or the regular command prompt, then you'll need to modiy the command slightly. In particular, the entire command would need to be on a single line. (Remove the '\' at the end of each line, and merge lines together, with space between the command-line flags) This will generate the new project in the current directory inside a folder named after the `artifactId` parameter. -After building the project, try running it to make sure that the migration worked. E.g. Assuming that your artifactId was myapp: +After building the project, try running it to make sure that the migration worked. For example, Assuming that your artifactId was myapp: -==== Command Line +==== Command line [source,bash,subs="+attributes"] ---- @@ -155,7 +155,7 @@ If All goes well, your app should open in the Codename One simulator. ==== NetBeans -IMPORTANT: Before opening the project in NetBeans, be sure to copy the files in the _tools/netbeans_ directory into the root directory. These are necessary for NetBeans to properly run, build, and debug the project. +IMPORTANT: Before opening the project in NetBeans, be sure to copy the files in the _tools/netbeans_ directory into the root directory. These are necessary for NetBeans to run, build, and debug the project. Open the `myapp` folder as a project in NetBeans. Then press the "Run" image:img/netbeans-run-icon.png[] button on the toolbar. @@ -175,7 +175,7 @@ In the next panel, press the _Browse_ button, and, in the file dialog, select th image::img/eclipse-import-browse-dialog.png[] -The next panel should look similar to the one below. Make sure all of the projects are "checked", and press _Finish_. +The next panel should look like the one below. Make sure all the projects are "checked," and press _Finish_. image::img/eclipse-import-list-projects-dialog.png[] @@ -195,7 +195,7 @@ Then check the _eclipse_ option, and press _Finish_ image::img/eclipse-import-launch-configurations-finish.png[] -The "Run" button menu should now include options for all of the major build targets. You can see them by pressing on the _Run_ button in the toolbar: +The "Run" button menu should now include options for all the major build targets. You can see them by pressing on the _Run_ button in the toolbar: image::img/eclipse-run-button-dropdown.png[] @@ -204,11 +204,11 @@ Select the _MyApp - Run Simulator_ option from this menu. If all goes well it should open in the Codename One simulator. -==== Example: Migrating Kitchen Sink App +==== Example: migrating kitchen sink app -Let us consider a concrete example, now. Download the KitchenSink Ant project from https://github.com/codenameone/KitchenSink/archive/refs/tags/v1.0-cn7.0.11.zip[here] and extract it. +Let's consider a concrete example, now. Download the KitchenSink Ant project from https://github.com/codenameone/KitchenSink/archive/refs/tags/v1.0-cn7.0.11.zip[here] and extract it. -The following is a bash script that uses curl to download this project as a zip file, and then converts it to a fully-functional Maven project. +The following is a bash script that uses curl to download this project as a zip file, and then converts it to a fully functional Maven project. [source,bash,subs="+attributes"] ---- @@ -227,13 +227,13 @@ mvn com.codenameone:codenameone-maven-plugin:${CN1_VERSION}:generate-app-project -DsourceProject=KitchenSink-1.0-cn7.0.11 ---- -NOTE: This command is formatted for the bash prompt (e.g. Linux or Mac). It will work on Windows also if you use bash. If you're on Windows and are using PowerShell or the regular command prompt, then you'll need to modiy the command slightly. In particular, the entire command would need to be on a single line. (Remove the '\' at the end of each line, and merge lines together, with space between the command-line flags) +NOTE: This command is formatted for the bash prompt (for example, Linux or Mac). It will work on Windows also if you use bash. If you're on Windows and are using PowerShell or the regular command prompt, then you'll need to modiy the command slightly. In particular, the entire command would need to be on a single line. (Remove the '\' at the end of each line, and merge lines together, with space between the command-line flags) This will generate the maven project in a directory named "kitchensink" in the current working directory because of the `-DartifactId=kitchensink` directory. [#project-dependencies] -=== Adding Project Dependencies +=== Adding project dependencies TIP: For the easiest and recommended approach to adding dependencies to your project, skip to <>. @@ -243,45 +243,45 @@ NOTE: See https://maven.apache.org/guides/introduction/introduction-to-dependenc With Codename One projects, there are a few caveats (see <>), and a few added nicities that make it easier to find and install add-on libraries in your project (see <>). -==== Which `pom.xml` Do I add the `` Snippet to? +==== Which `pom.xml` do I add the `` snippet to -Let us assume that you've a Maven `` snippet that you've copied from Maven central, and it's burning a hole in your clipboard while you're trying to figure out where to paste it into your project. +Let's assume that you've a Maven `` snippet that you've copied from Maven central, and it's burning a hole in your clipboard while you're trying to figure out where to paste it into your project. Codename One application projects, being multi-module projects, have more than one `pom.xml` file; One per module. -**Question:** Which pom.xml file do I paste my snippet into? +**Question:** Which pom.xml file do I paste your snippet into? **Answer:** common/pom.xml (almost always). -The "common" module is where nearly all of your Codename One application resides. It houses your Java and Kotlin files, your CSS files, your GUI builder files, your Codename One configuration files (i.e. `codenameone_settings.properties`). Pretty much everything. The things you'd place in the other modules (e.g. `Java SE`, `ios`, etc...) are your platform-specific native interface implementations; And in many applications you won't need any of that. +The "common" module is where all your Codename One application resides. It houses your Java and Kotlin files, your CSS files, your GUI builder files, your Codename One configuration files (that is, `codenameone_settings.properties`). Pretty much everything. The things you'd place in the other modules (for example, `Java SE`, `ios`, etc...) are your platform-specific native interface implementations; And in many applications you won't need any of that. -Therefore, when adding dependencies into your app, you'll almost always place them inside the pom.xml file for the "common" module. +So, when adding dependencies into your app, you'll almost always place them inside the pom.xml file for the "common" module. -TIP: You can add dependencies without needing to modify XML configuration files using the Control Center. See <>. +TIP: You can add dependencies without needing to change XML configuration files using the Control Center. See <>. .When to use the "other" pom.xml Files [sidebar] **** -The instructions say that we *almost* always add dependencies in the common/pom.xml file. So what are the other modules' pom.xml files for, and when do we need to modify them, or add dependencies to them? +The instructions say that you *almost* always add dependencies in the common/pom.xml file. what are the other modules' pom.xml files for, and when do you need to change them, or add dependencies to them? Here's an overview: %PROJECT_ROOT%/pom.xml:: -The root pom.xml file is the parent module of all of other modules. Anything you add here will be inherited by all of the modules. It can be helpful to use `` and `` sections in this file to consolidate versions for dependencies and plugins project-wide. This is also a good place to add project meta-data like ``, ``. +The root pom.xml file is the parent module of all other modules. Anything you add here will be inherited by all the modules. It can be helpful to use `` and `` sections in this file to merge versions for dependencies and plugins project-wide. This is also a good place to add project meta-data like ``, ``. Java SE/pom.xml:: -Any dependencies that are only required for native implementations on the Java SE platform can be added here. Dependencies added to this project aren't subject to <>. +Any dependencies that are only required for native implementations on the Java SE platform can be added here. Dependencies added to this project aren't subject to <>. + -Additionally, this module handles the build toolchain for the Java SE platform. This includes Mac and Windows Desktop builds, as well as Java SE desktop builds. If you want to customize the build workflow for any of these targets, you would do so by adding plugin executions in this pom.xml file. +Additionally, this module handles the build toolchain for the Java SE platform. This includes Mac and Windows Desktop builds, as well as Java SE desktop builds. If you want to customize the build workflow for any of these targets, you would do so by adding plugin executions in this pom.xml file. -android, ios, win, and javascript:: -These modules don't really use Maven for their dependencies (Android may deserve a small asterisk here, but that's complicated), so the primary thing you'd want to *modify* in these pom.xml files are the build toolchain for those targets. E.g. You might add plugin executions for your CI workflow on builds targeting these particular platforms. +android, ios, win, and JavaScript:: +These modules don't use Maven for their dependencies (Android may deserve a small asterisk here, but that's complicated), so the primary thing you'd want to *change* in these pom.xml files are the build toolchain for those targets. For example, You might add plugin executions for your CI workflow on builds targeting these particular platforms. **** [#maven-dependency-example] -==== Example: Adding Google Maps Dependency via Maven Central +==== Example: adding Google maps dependency via Maven central -Let us add the GoogleMaps library to our app as a maven dependency. +Let's add the GoogleMaps library to your app as a maven dependency. As described in the https://github.com/codenameone/codenameone-google-maps#maven-dependency[GoogleMaps cn1lib README], the dependency snippet is: @@ -295,13 +295,13 @@ As described in the https://github.com/codenameone/codenameone-google-maps#maven ---- -You should, however, look on https://search.maven.org/artifact/com.codenameone/googlemaps-lib[Maven central] to see what the latest version number is, and substitute that version into the `` tag of the snippet. +You should, but, look on https://search.maven.org/artifact/com.codenameone/googlemaps-lib[Maven central] to see what the latest version number is, and substitute that version into the `` tag of the snippet. Copy and paste this snippet into the `` section of your common/pom.xml file. And save it. [TIP] ==== -The common/pom.xml file has *a lot* of existing configuration in it, and it may not be clear, on first glance, where the `` tag is located. A simple "find" for `` may deliver you a red herring also, since there are a few `` tags which also include `` sections. +The common/pom.xml file has *a lot* of existing configuration in it, and it may not be clear, on first glance, where the `` tag is located. A simple "find" for `` may also lead you astray, since there are a few `` tags which also include `` sections. The *correct* `` section, is located near the top of the file. You can identify it because it will include the following comment: @@ -310,7 +310,7 @@ The *correct* `` section, is located near the top of the file. You ---- -This is a special marker that's used by some of the Codename One tooling to help it locate the optimal place to inject dependencies. +This is a special marker that's used by some Codename One tooling to help it locate the optimal place to inject dependencies. **don't REMOVE THIS COMMENT**. Just add your dependency snippet somewhere before or after it. ==== @@ -323,14 +323,14 @@ The easiest way to find compatible libraries is to use the <>). -If the compliance check fails (i.e. the app uses unsupported APIs), the build will fail. The error log should provide some clues as to where the offending code resides. +If the compliance check fails (that is, the app uses unsupported APIs), the build will fail. The error log should provide some clues about where the offending code resides. [#managing-addons-in-control-center] -==== Managing Add-Ons in Control Center +==== Managing Add-Ons in control center As I mention throughout this guide, the best place to find and install add-ons for your project is in the Codename One Control Center (aka Codename One Preferences. aka Codename One Settings). See <>. @@ -348,7 +348,7 @@ Type in "Maps" into the search box, and it should narrow the options down to thr image:img/control-center-extensions-search-maps.png[Control Center Extensions filtered on maps] -The one in the middle "Codename One Google Native", is the Google maps lib that we want. +The one in the middle `Codename One Google Native`, is the Google maps lib that you want. Press the "Download" button. @@ -357,16 +357,16 @@ You should see a progress indicator while performs the installation. .How Control Center Handles Maven Dependencies [sidebar] **** -Many of the extensions listed in the control center are deployed as cn1lib bundles. Others are deployed on Maven central and *could* simply be installed by adding a snippet into the pom.xml file (as described in <>). +Many of the extensions listed in the control center are deployed as cn1lib bundles. Others are deployed on Maven central and *could* be installed by adding a snippet into the pom.xml file (as described in <>). -The control center UI shields you from the details of how it installs the extensions into your app. For extensions that are deployed on Maven central, it will simply add the Maven dependency for the library directly into your project's common/pom.xml file. For extensions that are distributed as cn1lib bundles, it uses the `install-cn1lib` Maven goal to install it into your project. +The control center UI shields you from the details of how it installs the extensions into your app. For extensions that are deployed on Maven central, it will add the Maven dependency for the library directly into your project's common/pom.xml file. For extensions that are distributed as cn1lib bundles, it uses the `install-cn1lib` Maven goal to install it into your project. -You shouldn't need to worry about this, as it happens seamlessly. If you're curious, you can look at the `` section of your common/pom.xml file to see the added `` tag after you install an extension. +You shouldn't need to worry about this, as it happens seamlessly. If you're curious, you can look at the `` section of your common/pom.xml file to see the added `` tag after you install an extension. **** -==== Installing Legacy cn1libs +==== Installing legacy cn1libs -The recommended approach for installing add-ons to your project is to use the <>, or by <>. However, in some situations you may not be able to use those methods. E.g. If you've a legacy cnlib file that you need to use in your app, and it isn't available on Maven central or the control center. +The recommended approach for installing add-ons to your project is to use the <>, or by <>. But, in some situations you may not be able to use those methods. For example, If you've a legacy cnlib file that you need to use in your app, and it isn't available on Maven central or the control center. In cases like this you can use the `install-cn1lib` Maven goal to install it as follows: diff --git a/docs/developer-guide/Maven-Introduction.adoc b/docs/developer-guide/Maven-Introduction.adoc index 4ca0a1220d..a681295057 100644 --- a/docs/developer-guide/Maven-Introduction.adoc +++ b/docs/developer-guide/Maven-Introduction.adoc @@ -4,11 +4,11 @@ Codename One uses Maven as its primary build tool. This guide aims to be the def === Conventions -The instructions throughout this chapter provide parallel guidance for the supported development environments. Each subsection heading clearly identifies the target tooling so you can follow the directions that match your workflow: +The instructions throughout this chapter provide parallel guidance for the supported development environments. Each subsection heading identifies the target tooling so you can follow the directions that match your workflow: -* **Command Line (CLI)** – Focused on running Maven from a terminal. The commands use a Unix-style shell syntax; adapt the examples for Windows when necessary. -* **IntelliJ IDEA** – Step-by-step directions for working inside IntelliJ. -* **NetBeans** – Equivalent instructions tailored to NetBeans. -* **Eclipse** – Maven-based guidance for Eclipse users. +* **Command Line (CLI):** Focused on running Maven from a terminal. The commands use a Unix-style shell syntax; adapt the examples for Windows when necessary. +* **IntelliJ IDEA:** Step-by-step directions for working inside IntelliJ. +* **NetBeans:** Equivalent instructions tailored to NetBeans. +* **Eclipse:** Maven-based guidance for Eclipse users. -When you encounter environment-specific sections later in the guide, simply follow the subsection corresponding to your preferred tools. +When you reach environment-specific sections later in the guide, follow the subsection corresponding to your preferred tools. diff --git a/docs/developer-guide/Maven-Project-Templates.adoc b/docs/developer-guide/Maven-Project-Templates.adoc index d007ab6a91..3062af247b 100644 --- a/docs/developer-guide/Maven-Project-Templates.adoc +++ b/docs/developer-guide/Maven-Project-Templates.adoc @@ -1,11 +1,11 @@ [#creating-project-templates] -== Creating Project Templates +== Creating project templates A project template is a Codename One application project that can be used as a starting point for building a Codename One application. https://start.codenameone.com[Codename One initializr] uses project templates to generate starter projects for Codename One applications. You can also use the <> goal to generate starter projects from templates directly in Maven. Any Codename One project can be converted into a project template. -=== Converting a Codename One Application Project into a Project template +=== Converting a Codename One application project into a project template If you've an existing maven Codename One application project, you can convert it into a project template by adding a file named `generate-app-project.rpf` in the root directory of the project. @@ -43,11 +43,11 @@ Paste any maven dependencies that the parent project requires into this section. See <> for a more concrete example of the `generate-app-project.rpf`. -=== Test your Project Template +=== Test your project template You can test your project template by using it as the `sourceProject` parameter for the `generate-app-project` goal. See <>. -=== Add Your Template to Codename One Intializr +=== Add your template to Codename One intializr If you've a project template that you want to share with the community, please file an issue in the https://github.com/codenameone/CodenameOne/issues[Codename One issue tracker] with a link to a Github Repository of your project template, and request to have it added https://start.codenameone.com[Codename One initializr]. diff --git a/docs/developer-guide/Maven-Updating-Codename-One.adoc b/docs/developer-guide/Maven-Updating-Codename-One.adoc index 6a98d53a4a..c47f2d0e44 100644 --- a/docs/developer-guide/Maven-Updating-Codename-One.adoc +++ b/docs/developer-guide/Maven-Updating-Codename-One.adoc @@ -4,9 +4,7 @@ Codename One releases new versions weekly on Maven central. it's recommended tha You can use the <> to update both the Codename One libraries, and the Codename One dependencies in your project. -e.g. - -[source,bash] +for example, [source,bash] ---- mvn cn:update ---- @@ -14,7 +12,7 @@ mvn cn:update [discrete] === CLI -Alternatively you can use the `run.sh/run.bat` script to run this goal as follows: +Or you can use the `run.sh/run.bat` script to run this goal as follows: [source,bash] ---- @@ -26,7 +24,7 @@ NOTE: Use `run.bat update` instead on Windows [discrete] === IntelliJ -Alternatively you can click on the "Configuration" menu, and select "Tools" > "Update Codename One" as shown here: +Or you can click on the "Configuration" menu, and select "Tools" > "Update Codename One" as shown here: image::img/intellij-update-codenameone.png[] @@ -35,18 +33,18 @@ Then press the image:img/intellij-run-icon.png[Run] button. [discrete] === NetBeans -Alternatively you can right click on the project in the project inspector, and select "Run Maven" > "Update Codename One" as shown here: +Or you can right click on the project in the project inspector, and select "Run Maven" > "Update Codename One" as shown here: image::img/netbeans-update-codenameone.png[] Then press the image:img/netbeans-run-icon.png[Run] button. [discrete] -=== Manually Updating the pom.xml file +=== Manually updating the pom.xml file You can also update your Codename One dependencies manually by modifying the `cn1.version` and `cn1.plugin.version` properties defined in your project's `pom.xml` file. -E.g. Open the `pom.xml` file, and look for the following: +For example, Open the `pom.xml` file, and look for the following: [source,xml,subs="+attributes"] ---- diff --git a/docs/developer-guide/Miscellaneous-Features.asciidoc b/docs/developer-guide/Miscellaneous-Features.asciidoc index 6f452056b6..b0d317e1d1 100644 --- a/docs/developer-guide/Miscellaneous-Features.asciidoc +++ b/docs/developer-guide/Miscellaneous-Features.asciidoc @@ -1,8 +1,8 @@ -== Miscellaneous Features +== Miscellaneous features -=== Phone Functions +=== Phone functions -Most of the low-level phone functionality is accessible in the https://www.codenameone.com/javadoc/com/codename1/ui/Display.html[Display] class. Think of it as a global class that covers access to the "system". +Most of the low-level phone functionality is accessible in the https://www.codenameone.com/javadoc/com/codename1/ui/Display.html[Display] class. Think of it as a global class that covers access to the "system." ==== SMS @@ -21,7 +21,7 @@ try { Android supports sending SMS messages in the background without any UI. iOS doesn't provide that ability, so the best it can offer is to launch the native SMS app with your message composed for the user to send. Android supports that interactive flow as well (launching the OS native SMS app). -The default `sendSMS` API ignores that difference and simply works interactively on iOS while sending +The default `sendSMS` API ignores that difference and works interactively on iOS while sending in the background for Android when the platform allows it. The `getSMSSupport` API returns one of the following options: @@ -29,7 +29,7 @@ The `getSMSSupport` API returns one of the following options: - SMS_NOT_SUPPORTED - for desktop, tablet etc. - SMS_SEAMLESS - `sendSMS` won't show a UI and will send in the background - SMS_INTERACTIVE - `sendSMS` will show an SMS sending UI -- SMS_BOTH - `sendSMS` can support both seamless and interactive mode, this currently works on Android +- SMS_BOTH - `sendSMS` can support both seamless and interactive mode, this works on Android The `sendSMS` can accept an interactive argument: `sendSMS(String phoneNumber, String message, boolean interactive)` @@ -70,7 +70,7 @@ You can dial the phone by using: Display.getInstance().dial("+999999999"); ---- -==== Call Detection +==== Call detection Codename One includes a generic call detection API through `Display.isCallDetectionSupported()` and `Display.isInCall()`. @@ -80,7 +80,7 @@ On iOS, `isInCall()` is inferred from app interruption lifecycle events, which m Because iOS toggles this flag during app lifecycle transitions, polling it with a `UITimer` can miss the interruption window entirely (timers are paused while the app is inactive, and the flag is reset when the app becomes active again). If you need to react, check it from lifecycle callbacks such as your app's `stop()`/`start()` flow instead of periodic polling. -On Android, call detection is currently unsupported because robust detection would require invasive telephony permissions that are intentionally avoided. +On Android, call detection is unsupported because robust detection would require invasive telephony permissions that are intentionally avoided. [source,java] ---- @@ -114,9 +114,9 @@ m.getAttachments().put(imageAttachmentUri, "image/png"); Display.getInstance().sendMessage(new String[] {"someone@gmail.com"}, "Subject of message", m); ---- -NOTE: Some features such as attachments etc. don't work correctly in the simulator but should work on iOS/Android +NOTE: Some features such as attachments etc. don't work in the simulator but should work on iOS/Android -=== Larger Text Accessibility +=== Larger text accessibility Codename One can adapt to the operating system’s larger text accessibility setting. The https://www.codenameone.com/javadoc/com/codename1/ui/Display.html[Display] APIs expose two signals: `isLargerTextEnabled()` indicates whether the user has enabled larger text, and `getLargerTextScale()` returns the scale factor that should be applied to fonts. @@ -144,10 +144,10 @@ if (display.isLargerTextEnabled()) { ==== How the scale is computed per platform -* *iOS* — the scale is the ratio between the body text size at the user's current Dynamic Type setting and the iOS standard 17pt body size, i.e. `[UIFont preferredFontForTextStyle:UIFontTextStyleBody].pointSize / [UIFont systemFontSize]`. Scales for the standard slider stops range from `0.82×` (Extra Small) through `1.00×` (Large, default) up to `1.35×` (Extra Extra Extra Large). When the user enables _Larger Accessibility Sizes_ in Settings → Accessibility → Display & Text Size → Larger Text, the slider extends with five additional stops whose scales reach as high as `3.12×`. These values match what every native iOS app sees from `UIFontMetrics`. -* *Android* — the scale is `Configuration.fontScale`, which is bucketed by Android (typically `0.85`, `1.0`, `1.15`, `1.30`). +* *iOS:* the scale is the ratio between the body text size at the user's current Dynamic Type setting and the iOS standard 17pt body size, that is, `[UIFont preferredFontForTextStyle:UIFontTextStyleBody].pointSize / [UIFont systemFontSize]`. Scales for the standard slider stops range from `0.82×` (Extra Small) through `1.00×` (Large, default) up to `1.35×` (Extra Extra Large). When the user enables _Larger Accessibility Sizes_ in Settings → Accessibility → Display & Text Size → Larger Text, the slider extends with five more stops whose scales reach as high as `3.12×`. These values match what every native iOS app sees from `UIFontMetrics`. +* *Android:* the scale is `Configuration.fontScale`, which is bucketed by Android (typically `0.85`, `1.0`, `1.15`, `1.30`). -NOTE: Because the scale is multiplicative, a base font size that is already larger than the platform default will compound. For example, a `defaultFontSizeInt: 18` in your CSS is roughly 6% larger than iOS's 17pt body, so at iOS Dynamic Type _Extra Extra Extra Large_ (`1.35×`) your text will render at roughly `24.3pt` versus a native iOS app's `23pt`. If you intentionally use a base size larger than the platform default and you also enable `useLargerTextScaleBool`, expect the absolute size at high accessibility settings to be visibly larger than typical native apps. Many native iOS apps cap Dynamic Type via `UIFontMetrics maximumPointSize:` for non-text-heavy UIs to avoid layout breakage. +NOTE: Because the scale is multiplicative, a base font size that's already larger than the platform default will compound. For example, a `defaultFontSizeInt: 18` in your CSS is 6% larger than iOS's 17pt body, so at iOS Dynamic Type _Extra Extra Large_ (`1.35×`) your text will render at `24.3pt` versus a native iOS app's `23pt`. If you intentionally use a base size larger than the platform default and you also enable `useLargerTextScaleBool`, expect the absolute size at high accessibility settings to be visibly larger than typical native apps. Many native iOS apps cap Dynamic Type via `UIFontMetrics maximumPointSize:` for non-text-heavy UIs to avoid layout breakage. ==== Previewing in the simulator @@ -162,7 +162,7 @@ The contacts API provides you with the means to query the phone’s address book Notice that on some platforms this will prompt the user for permissions and the user might choose not to grant that permission. To detect whether this is the case you can invoke `isContactsPermissionGranted()` after invoking `getAllContacts()`. This can help you adapt your error message to the user. -Once you've a https://www.codenameone.com/javadoc/com/codename1/contacts/Contact.html[Contact] you can use the `getContactById` method, however the default method is a bit slow if you want to pull a large batch of contacts. The solution for this is to extract the data that you need through +Once you've a https://www.codenameone.com/javadoc/com/codename1/contacts/Contact.html[Contact] you can use the `getContactById` method, but the default method is a bit slow if you want to pull a large batch of contacts. The solution for this is to extract the data that you need through [source,java] ---- @@ -173,9 +173,9 @@ getContactById(String id, boolean includesFullName, Here you can specify true for the attributes that actually matter to you. -Another capability of the contacts API is the ability to extract all of the contacts quickly. This isn't supported on all platforms but platforms such as Android can get a boost from this API as extracting the contacts one by one is remarkably slow on Android. +Another capability of the contacts API is the ability to extract all the contacts. This isn't supported on all platforms but platforms such as Android can get a boost from this API as extracting the contacts one by one is slow on Android. -You can check if a platform supports the extraction of all the contacts quickly thru `ContactsManager.isAllContactsFast()`. +You can check if a platform supports the extraction of all the contacts thru `ContactsManager.isAllContactsFast()`. IMPORTANT: When retrieving all the contacts, notice that you should probably not retrieve all the data and should set some fields to false to perform a more efficient query @@ -206,7 +206,7 @@ image::img/contacts-list.png[List of contacts,scaledwidth=20%] Notice that you didn't fetch the image of the contact as the performance of loading these images might be prohibitive. You can enhance the code above to include images by using slightly more complex code such as this: -TIP: The `scheduleBackgroundTask` method is similar to `new Thread()` in some regards. It places elements in a queue instead of opening too many threads so it can be good for non-urgent tasks +TIP: The `scheduleBackgroundTask` method is like `new Thread()` in some regards. It places elements in a queue instead of opening too many threads so it can be good for non-urgent tasks [source,java] ---- @@ -249,7 +249,7 @@ TIP: Notice that the code above uses `callSerially` & `scheduleBackgroundTask` i You can use `createContact(String firstName, String familyName, String officePhone, String homePhone, String cellPhone, String email)` to add a new contact and deleteContact(String id) to delete a contact. -=== Localization & Internationalization (L10N & I18N) +=== Localization & internationalization (L10N & I18N) Localization (l10n) means adapting to a locale which is more than translating to a specific language but also to a specific language within environment for example: `en_US!= en_UK`. Internationalization (i18n) is the process of creating one application that adapts to all locales and regional requirements. @@ -292,7 +292,7 @@ Enumeration locales = res.listL10NLocales( "l10n" ); An exception for localization is the `TextField`/`TextArea` components both of which contain user data, in those cases the text won't be localized to avoid accidental localization of user input. -TIP: You can export and import resource bundles as standard Java properties files, CSV and XML. The formats are pretty standard for most localization shops, the XML format Codename One supports is the one used by Android’s string bundles which means most localization specialists should easily localize it +TIP: You can export and import resource bundles as standard Java properties files, CSV and XML. The formats are pretty standard for most localization shops, the XML format Codename One supports is the one used by Android’s string bundles which means most localization specialists should localize it The resource bundle is a map between keys and values for example: the code below displays `"This Label is localized"` on the `Label` with the hardcoded resource bundle. It would work the same with a resource bundle loaded from a resource file: @@ -309,7 +309,7 @@ hi.show(); .Localized label image::img/l10n-basic.png[Localized label,scaledwidth=30%] -==== Localization Manager +==== Localization manager The https://www.codenameone.com/javadoc/com/codename1/l10n/L10NManager.html[L10NManager] class includes a multitude of features useful for common localization tasks. @@ -345,7 +345,7 @@ image::img/l10n-manager.png[Localization formatting/parsing and information,scal RTL stands for right to left, in the world of internationalization it refers to languages that are written from right to left (Arabic, Hebrew, Syriac, Thaana). -Most western languages are written from left to right (LTR), however some languages are written from right to left (RTL) speakers of these languages expect the UI to flow in the opposite direction otherwise it seems weird like reading this word would be to most English speakers: "drieW". +Most western languages are written from left to right (LTR), but some languages are written from right to left (RTL) speakers of these languages expect the UI to flow in the opposite direction otherwise it seems weird like reading this word would be to most English speakers: "drieW." The problem posed by RTL languages is known as BiDi (Bi-directional) and not as RTL since the "true" problem isn't the reversal of the writing/UI but rather the mixing of RTL and LTR together. For example: numbers are always written from left to right ( like in English) so in an RTL language the direction is from right to left and once you reach a number or English text embedded in the middle of the sentence (such as a name) the direction switches for a duration and is later restored. @@ -372,18 +372,18 @@ Most of Codename One's RTL support is under the hood, the https://www.codenameon Once RTL is activated all positions in Codename One become reversed and the UI becomes a mirror of itself. For example: Adding a `Toolbar` command to the left will actually make it appear on the right. Padding on the left becomes padding on the right. The scroll moves to the left etc. -This applies to the layout managers (except for group layout) and most components. Bidi is mostly seamless in Codename One but a developer still needs to be aware that his UI might be mirrored for these cases. +This applies to the layout managers (except for group layout) and most components. Bidi is seamless in Codename One but a developer still needs to be aware that his UI might be mirrored for these cases. -==== Localizing Native iOS Strings +==== Localizing native iOS strings Some strings in iOS need to be localized using iOS's native mechanisms - namely providing _*.lproj_ directories with _.strings_ files. For example, if you want the app to have a different bundle display name for each language, or you want to translate the "UsageDescription" strings of your Info.plist into many languages, you would need to use iOS' https://developer.apple.com/localization/[native localization facilities]. -===== Example: Localizing the App Name +===== Example: localizing the app name -The app name, as it's displayed to the user, is defined in using the _CFBundleDisplayName_ key of the app's Info.plist file. Normally, this will be automatically set to your app's display name, as defined in your _codenameone_settings.properties_ file. This works fine if your app will have the same name in every locale, but suppose you want your app to take on a different name in French than in English. For example: You want your app to be called "Hello App" for English-speaking users, and "Bonjour App" for French-speaking users. +The app name, as it's displayed to the user, is defined in using the _CFBundleDisplayName_ key of the app's Info.plist file., this will be automatically set to your app's display name, as defined in your _codenameone_settings.properties_ file. This works fine if your app will have the same name in every locale, but suppose you want your app to take on a different name in French than in English. For example: You want your app to be called "Hello App" for English-speaking users, and "Bonjour App" for French-speaking users. -In this case, you need to add iOS localization bundles "en.lproj" and "fr.lproj", each with a file named "InfoPlist.strings". If you're using Maven, then you can add these directly inside the _ios/src/main/strings_ directory of your project. +In this case, you need to add iOS localization bundles "en.lproj" and `fr.lproj`, each with a file named `InfoPlist.strings`. If you're using Maven, then you can add these directly inside the _ios/src/main/strings_ directory of your project. TIP: You will need to create the _strings_ directory manually, if it doesn't exist yet. @@ -406,28 +406,28 @@ image::img/ios_strings_directory_screenshot.png[] [NOTE] ==== -The _strings_ format is similar to the _properties_ file format, except that both the "key" and the "value" must be wrapped in quotes. And if there are many strings, then they must be delimited by a semi-colon `;`. +The _strings_ format is like the _properties_ file format, except that both the "key" and the "value" must be wrapped in quotes. And if there are many strings, then they must be delimited by a semi-colon `;`. ==== .Legacy Ant Projects **** -Legacy Ant projects have a different directory structure. They have no equivalent location to the Maven _ios/src/main/strings_ directory, but the legacy `ios/src/main/resources` content can be replicated under _native/ios_. To include native iOS localizations in those projects, place zipped versions of your .lproj directories inside the `native/ios` directory. E.g. _en.lproj.zip_, _fr.lproj.zip_, etc. +Legacy Ant projects have a different directory structure. They have no equivalent location to the Maven _ios/src/main/strings_ directory, but the legacy `ios/src/main/resources` content can be replicated under _native/ios_. To include native iOS localizations in those projects, place zipped versions of your .lproj directories inside the `native/ios` directory. For example, _en.lproj.zip_, _fr.lproj.zip_, etc. TIP: Whenever possible, migrate legacy builds to Maven to take advantage of the modern workflow. See <> for automated migration options. **** -===== Example: Localization App Usage Description Strings +===== Example: localization app usage description strings -iOS requires you to supply usage descriptions for many features that will be displayed to the user when the app requests permission to use the feature. For example, the https://developer.apple.com/documentation/bundleresources/information_property_list/nscamerausagedescription?language=objc[NSCameraUsageDescription] string must be provided if your app needs to use the camera. You can specify these values as build hints using the pattern `ios.NSXXXUsageDescription=This feature is needed blah blah blah`. In the `NSCameraUsageDescription` case, you might include the build hint: +iOS requires you to supply usage descriptions for many features that will be displayed to the user when the app requests permission to use the feature. For example, the https://developer.apple.com/documentation/bundleresources/information_property_list/nscamerausagedescription?language=objc[NSCameraUsageDescription] string must be provided if your app needs to use the camera. You can specify these values as build hints using the pattern `ios.NSXXXUsageDescription=This feature is needed blah blah`. In the `NSCameraUsageDescription` case, you might include the build hint: [source,properties] ---- ios.NSCameraUsageDescription=This app needs to use your camera to scan bar codes ---- -Ultimately these descriptions are embedded in your app's Info.plist file, so they can be localized the same way you localize other Info.plist values - in the localized _InfoPlist.strings_ file. +These descriptions are embedded in your app's Info.plist file, so they can be localized the same way you localize other Info.plist values - in the localized _InfoPlist.strings_ file. -See <<_example_localizing_the_app_name, the above example>> for instructions on localizing values in the Info.plist file. Then simply add translations to the _InfoPlist.strings_ file for your usage descriptions. +See <<_example_localizing_the_app_name, the above example>> for instructions on localizing values in the Info.plist file. Then add translations to the _InfoPlist.strings_ file for your usage descriptions. .ios/src/main/strings/en.lproj/InfoPlist.strings [source,strings] @@ -443,9 +443,9 @@ See <<_example_localizing_the_app_name, the above example>> for instructions on "NSCameraUsageDescription"="Cette application doit utiliser votre appareil photo pour scanner les codes à barres"; ---- -==== Localizing the App Icon +==== Localizing the app icon -Codename One can ship a different launcher icon per locale on iOS and Android by detecting specially-named PNG files during the build. Drop your per-locale artwork into `common/src/main/resources` using the convention: +Codename One can ship a different launcher icon per locale on iOS and Android by detecting specially named PNG files during the build. Drop your per-locale artwork into `common/src/main/resources` using the convention: [source,text] ---- @@ -454,38 +454,38 @@ cn1_icon_[_].png `` is a two-letter ISO 639-1 language code and the optional `` is a two-letter ISO 3166-1 country code. Language is treated as case-insensitive; the country component is normalized to uppercase. A few valid examples: -* `cn1_icon_fr.png` — French (any region) -* `cn1_icon_en_GB.png` — British English -* `cn1_icon_es_MX.png` — Mexican Spanish +* `cn1_icon_fr.png`: French (any region) +* `cn1_icon_en_GB.png`: British English +* `cn1_icon_es_MX.png`: Mexican Spanish Supply a square source image at least 432×432 pixels (the largest size emitted for Android adaptive icons); the build resizes it to every target density. The default app icon continues to be controlled by your `codenameone_settings.properties` file and is used whenever the device locale doesn't match any of the localized variants. At runtime the builders look for a `_` match first, then fall back to a bare `` match. Providing both (for example `cn1_icon_en.png` plus `cn1_icon_en_GB.png`) lets you give British users a country-specific icon while every other English locale still receives the generic English icon. -===== Android Behaviour +===== Android behaviour On Android the build generates locale-qualified drawable resources at every density so the platform picks the right icon automatically based on the device's current locale: * `drawable-[-r][-]/icon.png` at 36, 48, 72, 96, 128, 144 and 192 pixels for legacy launchers. * When `android.enableAdaptiveIcons=true` the same variants are also written as `mipmap-[-r]-/ic_launcher.png` and `ic_launcher_foreground.png`. The default adaptive background and the `mipmap-anydpi-v26/ic_launcher.xml` definition are reused as-is. -No code changes are required — Android's resource framework switches icons when the locale changes. +No code changes are required—Android's resource framework switches icons when the locale changes. -===== iOS Behaviour +===== iOS behaviour -iOS does not localize launcher icons natively, so Codename One wires up https://developer.apple.com/documentation/uikit/uiapplication/2806818-setalternateiconname[alternate app icons] for you: +iOS doesn't localize launcher icons natively, so Codename One wires up https://developer.apple.com/documentation/uikit/uiapplication/2806818-setalternateiconname[alternate app icons] for you: * For each detected locale the build generates `AppIcon__@2x.png` (120×120), `@3x.png` (180×180), `@2x~ipad.png` (152×152) and `83.5x83.5@2x~ipad.png` (167×167 for iPad Pro) in the app bundle root. * A `CFBundleIcons` (and `CFBundleIcons~ipad`) entry is injected into `Info.plist` containing a `CFBundleAlternateIcons` dictionary with one entry per locale. `CFBundlePrimaryIcon` continues to reference the default `iPhone7App`/`iPadApp7` image families. * The `CodenameOne_GLAppDelegate` is patched to call `-[UIApplication setAlternateIconName:completionHandler:]` at launch. The delegate reads `[NSLocale preferredLanguages]`, tries the full `_` key first, then falls back to the language-only key, and clears the alternate icon (reverting to the default) if no variant matches. * The injection is idempotent and runs before the `ios.afterFinishLaunching` hook, so any custom code you supply via that build hint is unaffected. -NOTE: iOS briefly displays a system alert the first time an app switches to an alternate icon. This is platform-standard behaviour — Codename One cannot suppress it. +NOTE: iOS displays a system alert the first time an app switches to an alternate icon. This is platform-standard behaviour—Codename One can't suppress it. -===== Tips and Troubleshooting +===== Tips and troubleshooting * File names are matched case-insensitively. `cn1_icon_EN_uk.png` is treated the same as `cn1_icon_en_UK.png`. * Names that don't fit the `<2-letter lang>[_<2-letter country>]` pattern are logged and skipped; the rest of the build continues. * The original `cn1_icon_*.png` files are removed after processing so they aren't shipped as stray bundle resources. -* To override the default (non-localized) icon, keep using the `Icon` setting in `codenameone_settings.properties` or the `icon` image in `CN1Resource.res` — this feature only adds *alternate* icons for specific locales. +* To override the default (non-localized) icon, keep using the `Icon` setting in `codenameone_settings.properties` or the `icon` image in `CN1Resource.res`. This feature only adds *alternate* icons for specific locales. === Location - GPS @@ -504,7 +504,7 @@ IMPORTANT: In order for location to work on iOS you *MUST* define the build hint The `getCurrentLocationSync()` method is good for cases where you need to fetch a current location once and not repeatedly query location. It activates the GPS then turns it off to avoid excessive battery usage. For example, if an application needs to track motion or position over time it should use the location listener API to track location as such: -TIP: Notice that there is a method called `getCurrentLocation()` which will return the current state immediately and might not be accurate for some cases. +TIP: Notice that there is a method called `getCurrentLocation()` which will return the current state and might not be right for some cases. [source,java] ---- @@ -522,24 +522,24 @@ LocationManager.getLocationManager().setLocationListener(new MyListener()); IMPORTANT: On Android location maps to low-level API's if you disable the usage of Google Play Services. By default location should perform well if you leave the Google Play Services on -==== Location In The Background - Geofencing +==== Location in the background - geofencing -Polling location is generally expensive and requires a special permission on iOS. Its also implemented rather differently both in iOS and Android. Both platforms place restrictions on the location API usage in the background. +Polling location is expensive and requires a special permission on iOS. Its also implemented rather differently both in iOS and Android. Both platforms place restrictions on the location API usage in the background. Because of the nature of background location the API is non-trivial. It starts with the venerable `LocationManager` but instead of using the standard API you need to use `setBackgroundLocationListener`. Instead of passing a `LocationListener` instance you need to pass a `Class` object instance. This is important because background location might be invoked when the app isn't running and an object would need to be allocated. -Notice that you should *NOT* perform long operations in the background listener callback. IOS wake-up time is limited to approximately 10 seconds and the app could get killed if it exceeds that time slice. +Notice that you should *NOT* perform long operations in the background listener callback. IOS wake-up time is limited to about 10 seconds and the app could get killed if it exceeds that time slice. -Notice that the listener can also send events when the app is in the foreground, therefore it's recommended to check the app state before deciding how to process this event. You can use `Display.isMinimized()` to determine if the app is currently running or in the background. +Notice that the listener can also send events when the app is in the foreground, so it's recommended to check the app state before deciding how to process this event. You can use `Display.isMinimized()` to determine if the app is running or in the background. When implementing this make sure that: - The class passed to the API is a public class in the global scope. Not an inner class or anything like that! - The class has a public no-argument constructor - You need to pass it as a class literal for example: `MyClassName.class`. don't use `Class.forName("my.package.MyClassName")`! + -Class names are problematic since device builds are obfuscated, you should use literals which the obfuscator detects and handles correctly. +Class names are problematic since device builds are obfuscated, you should use literals which the obfuscator detects and handles. The following code demonstrates usage of the GeoFence API: @@ -552,11 +552,11 @@ LocationManager.getLocationManager() .addGeoFencing(GeofenceListenerImpl.class, gf); ---- -===== Android Background Location Permissions (API 30+) +===== Android background location permissions (API 30+) -On Android 11 (API level 30) and higher, requesting background location permission requires a two-step process. First, foreground location permissions must be granted. Then, the app must request background location access, which will direct the user to the system settings to select "Allow all the time". Codename One handles this flow automatically when you use `LocationManager`. +On Android 11 (API level 30) and higher, requesting background location permission requires a two-step process. First, foreground location permissions must be granted. Then, the app must request background location access, which will direct the user to the system settings to select `Allow all the time`. Codename One handles this flow automatically when you use `LocationManager`. -For Android 11+ (API 30+), Codename One detects if background location is needed and presents a dialog explaining the requirement before redirecting the user to the app settings. You can customize the permission prompt message using the localization key `android.permission.ACCESS_BACKGROUND_LOCATION`. +For Android 11+ (API 30+), Codename One detects if background location is needed and presents a dialog explaining the need before redirecting the user to the app settings. You can customize the permission prompt message using the localization key `android.permission.ACCESS_BACKGROUND_LOCATION`. [source,java] ---- @@ -581,7 +581,7 @@ public class GeofenceListenerImpl implements GeofenceListener { } ---- -=== Background Music Playback +=== Background music playback Codename One supports playing music in the background (for example: when the app is minimized) which is useful for developers building a music player style application. @@ -593,7 +593,7 @@ For iOS you will need to use a special build hint: `ios.background_modes=music`. Which should allow background playback of music on iOS and would work with the `createBackgroundMedia()` method. -=== Capture - Photos, Video, Audio +=== Capture - photos, video, audio The capture API allows you to use the camera to capture photographs or the microphone to capture audio. It even includes an API for video capture. + The API itself couldn’t be simpler: @@ -658,11 +658,11 @@ hi.show(); image::img/capture-photo.png[Captured photos previewed in the ImageViewer,scaledwidth=20%] // HTML_ONLY_START -You demonstrate video capture in the https://www.codenameone.com/manual/components.html#mediamanager-section[MediaManager section]. +You show video capture in the https://www.codenameone.com/manual/components.html#mediamanager-section[MediaManager section]. // HTML_ONLY_END //// //PDF_ONLY -You demonstrate video capture in the <>. +You show video capture in the <>. //// The sample below captures audio recordings (using the 'Capture' API) and copies them locally under unique names. It also demonstrates the storage and organization of captured audio: @@ -724,7 +724,7 @@ hi.show(); .Captured recordings in the demo image::img/capture-audio.png[Captured recordings in the demo,scaledwidth=20%] -Alternatively, you can use the `Media`, `MediaManager` and `MediaRecorderBuilder` APIs to capture audio, as a more customizable approach than using the Capture API: +Or, you can use the `Media`, `MediaManager` and `MediaRecorderBuilder` APIs to capture audio, as a more customizable approach than using the Capture API: [source,java] ---- @@ -930,7 +930,7 @@ Alternatively, you can use the `Media`, `MediaManager` and `MediaRecorderBuilder image::img/media-audio-recording-example.png[Example of recording and playback audio using Media API] -==== Capture Asynchronous API +==== Capture asynchronous API The `Capture` API also includes a callback based API that uses the `ActionListener` interface to implement capture. For example: you can adapt the previous sample to use this API as such: @@ -1004,9 +1004,9 @@ TIP: There is no need for a screenshot as it will look identical to the capture The last value is the type of content picked which can be one of: `Display.GALLERY_ALL`, `Display.GALLERY_VIDEO` or `Display.GALLERY_IMAGE`. -=== Analytics Integration +=== Analytics integration -One of the features in Codename One is built-in support for analytic instrumentation. Currently Codename One has built-in support for https://www.google.com/analytics/[Google Analytics], which provides reasonable enough statistics of application usage. +One of the features in Codename One is built-in support for analytic instrumentation. Codename One has built-in support for https://www.google.com/analytics/[Google Analytics], which provides reasonable enough statistics of application usage. Analytics is pretty seamless for the old GUI builder since navigation occurs through the Codename One API and can be logged without developer interaction. For example, to begin the instrumentation one needs to add the line: @@ -1018,17 +1018,17 @@ AnalyticsService.init(agent, domain); To get the value for the agent value create a Google Analytics account and add a domain, then copy and paste the string that looks something like UA-99999999-8 from the console to the agent string. Once this is in place you should start receiving statistic events for the application. -If your application isn't a GUI builder application or you would like to send more detailed data you can use the `Analytics.visit()` method to indicate that you're entering a specific page. +If your application isn't a GUI builder application or you would like to send more detailed data you can use the `Analytics.visit()` method to show that you're entering a specific page. -==== Application Level Analytics +==== Application level analytics In 2013 Google introduced an improved application level analytics API that's specifically built for mobile apps. For example, it requires a slightly different API usage. You can activate this specific mode by invoking `setAppsMode(true)`. When using this mode you can also report errors and crashes to the Google analytics server using the `sendCrashReport(Throwable, String message, boolean fatal)` method. -You generally recommend using this mode and setting up an apps analytics account as the results are more refined. +You recommend using this mode and setting up an apps analytics account as the results are more refined. -==== Overriding The Analytics Implementation +==== Overriding the analytics implementation The Analytics API can also be enhanced to support any other form of analytics solution of your own choosing by deriving the `AnalyticsService` class. @@ -1041,7 +1041,7 @@ AnalyticsService.init(new MyAnalyticsServiceSubclass()); Notice that this removes the need to invoke the other `init` method or `setAppsMode(boolean)`. -=== Native Facebook Support +=== Native Facebook support // HTML_ONLY_START TIP: Check out the https://www.codenameone.com/manual/components.html#sharebutton-section[ShareButton section] it might be enough for most of your needs. @@ -1053,7 +1053,7 @@ TIP: Check out the <> it might be enoug Codename One supports Facebooks https://www.codenameone.com/javadoc/com/codename1/io/Oauth2.html[Oauth2] login and Facebooks single sign on for iOS and Android. -==== Getting Started - Web Setup +==== Getting started - web setup To get started first you will need to create a facebook app on the Facebook developer portal at https://developers.facebook.com/apps/ @@ -1094,7 +1094,7 @@ IMPORTANT: The activity name should match the main class name followed by the wo .Android Activity definition image::img/chat-app-tutorial-facebook-login-7.png[Details,scaledwidth=50%] -To build the native Android app you must make sure that you setup the keystore correctly for your application. If you don't have +To build the native Android app you must make sure that you setup the keystore for your application. If you don't have an Android certificate you can use the visual wizard (in the Android section in the project preferences the button labeled #Generate#) or use the command line: [source,bash] @@ -1127,9 +1127,9 @@ Lastly you need to publish the Facebook app by flipping the switch in the apps " .Without flipping the switch the app won't "appear" image::img/chat-app-tutorial-facebook-login-9.png[Enable The App,scaledwidth=50%] -==== IDE Setup +==== IDE setup -You now need to set some important build hints in the project so it will work correctly. To set the build hints right click the project select project properties and in the Codename One section pick the second tab. Add this entry into the table: +You now need to set some important build hints in the project so it will work. To set the build hints right click the project select project properties and in the Codename One section pick the second tab. Add this entry into the table: [source,bash] ---- @@ -1169,14 +1169,14 @@ if(!fb.isUserLoggedIn()){ } ---- -IMPORTANT: All of these values are from the web version of the app! + +IMPORTANT: All these values are from the web version of the app! + They are used in the simulator and on "unsupported" platforms as a fallback. Android and iOS will use the native login -==== Facebook Publish Permissions +==== Facebook publish permissions -In order to post something to Facebook you need to request a write permission, you can do write operations +To post something to Facebook you need to request a write permission, you can do write operations within the callback which is invoked when the user approves the permission. You can prompt the user for publish permissions by using this code on a logged in https://www.codenameone.com/javadoc/com/codename1/social/FacebookConnect.html[FacebookConnect]: @@ -1198,7 +1198,7 @@ TIP: Notice that this won't always prompt the user, but its required to verify t [[google-login-section]] === Google Sign-In -Google Login is a bit of a moving target, as they are regularly creating new APIs and deprecating old ones. Codename One 3.7 and earlier used the Google+ API for sign-in, which is now deprecated. While this API still works, it's no longer useful on iOS as it redirects to Safari to perform login, and Apple no longer allows this practice. +Google Login is a bit of a moving target, as they are creating new APIs and deprecating old ones. Codename One 3.7 and earlier used the Google+ API for sign-in, which is now deprecated. While this API still works, it's no longer useful on iOS as it redirects to Safari to perform login, and Apple no longer allows this practice. The new, approved API is called Google Sign-In. Rather than using Safari to handle login (on iOS), it uses an embedded web view, which *is* permitted by Apple. @@ -1213,7 +1213,7 @@ The process involves four parts: [[ios-setup]] -==== iOS Setup Instructions +==== iOS setup instructions **Short Version** @@ -1251,7 +1251,7 @@ You'll be presented with the following screen .Choose and Configure Services form image::img/google-signin-ios-choose-and-configure-services-form.png[Choose and Configure Services form,scaledwidth=50%] -Click on "Google Sign-In". +Click on `Google Sign-In`. Then press the "Enable Google Sign-In" button that appears. @@ -1263,7 +1263,7 @@ You should then be presented with another button to "Generate Configuration File .Generate Configuration Files image::img/google-signin-ios-generate-configuration-files-button.png[Generate Configuration Files,scaledwidth=20%] -Finally you will be presented with a button to "Download GoogleServices-Info.plist". +You will be presented with a button to `Download GoogleServices-Info.plist`. .Download GoogleService-Info.plist file image::img/google-signin-ios-download-googleservice-infoplist-btn.png[Download GoogleService-Info plist file,scaledwidth=20%] @@ -1276,7 +1276,7 @@ image::img/google-signin-ios-google-service-info-plist-file-structure.png[Projec At this point, your app should be able to use Google Sign-In. Notice that you don't require any build hints. Only that the GoogleService-Info.plist file is added to the project's native/ios directory. [[android-setup]] -==== Android Setup Instructions +==== Android setup instructions **Short Version** @@ -1321,7 +1321,7 @@ The value that you enter here should be obtained from the certificate that you'r $ keytool -exportcert -alias myAlias -keystore /path/to/my-keystore.keystore -list -v ---- -The snippet above assumes that your keystore is located at `/path/to/my-keystore.keystore`, and the certificate alias is "myAlias". You'll be prompted to enter the password for your keystore, then the output will look something like: +The snippet above assumes that your keystore is located at `/path/to/my-keystore.keystore`, and the certificate alias is "myAlias." You'll be prompted to enter the password for your keystore, then the output will look something like: ---- Alias name: myAlias @@ -1369,7 +1369,7 @@ Press this button and you'll be presented with another button to "Generate Confi .Generate Configuration Files image::img/google-signin-ios-generate-configuration-files-button.png[Generate Configuration Files,scaledwidth=20%] -Finally you will be presented with a button to "Download google-services.json". +You will be presented with a button to `Download google-services.json`. .Download google-services.json file image::img/google-signin-android-download-googleservices-json-btn.png[Download google-services json file,scaledwidth=20%] @@ -1381,16 +1381,16 @@ image::img/google-signin-android-google-services-json-file-structure.png[Project At this point, your app should be able to use Google Sign-In. Notice that you don't require any build hints. Only that the google-services.json file is added to the project's native/android directory. -IMPORTANT: If you want to access additional information about the logged in user using Google's REST APIs, you will require an OAuth2.0 client ID of type Web Application for this project as well. See <> for details. +IMPORTANT: If you want to access more information about the logged in user using Google's REST APIs, you will require an OAuth2.0 client ID of type Web Application for this project as well. See <> for details. [[oauth-setup]] -==== OAuth Setup (Simulator and REST API Access) +==== OAuth setup (simulator and REST API access) -Getting Google Sign-In to work in the Codename One simulator requires an additional step after you've set up iOS and/or Android apps. The Simulator can't use the native Google Sign-In APIs, so it uses the standard Web Application OAuth2.0 API. In addition, the Android App requires a Web Application OAuth2.0 client ID to access additional Google REST APIs. +Getting Google Sign-In to work in the Codename One simulator requires an extra step after you've set up iOS and/or Android apps. The Simulator can't use the native Google Sign-In APIs, so it uses the standard Web Application OAuth2.0 API. Also, the Android App requires a Web Application OAuth2.0 client ID to access more Google REST APIs. -If you've set up the Google Sign-In API for either Android or iOS, then Google will have already automatically generated a Web Application OAuth2.0 client ID for you. You need to provide the ClientID and ClientSecret to the `GoogleConnect` instance (in your java code). +If you've set up the Google Sign-In API for either Android or iOS, then Google will have already automatically generated a Web Application OAuth2.0 client ID for you. You need to provide the ClientID and ClientSecret to the `GoogleConnect` instance (in your Java code). -===== Client ID, Client Secret and Redirect URL +===== Client ID, client secret and redirect URL . Log into https://console.cloud.google.com/apis[the Google Cloud Platform API console]. . Select your app from the drop-down-menu in the top bar @@ -1400,12 +1400,12 @@ image::img/google-sign-in-google-cloud-platform-credentials.png[Credentials,scal . Under the "OAuth2.0 Client IDs", find the row with "Web application" listed in the type column . Click the "Edit icon for that row. . Make note of the "Client ID" and "Client Secret" on this page, as you'll need to add them to your Java source in the next step. -. In the "Authorized redirect URIs" section, you will need to enter the URL to the page that the user will be sent to after a successful login. This page will appear in the simulator for a split second, as Codename One's BrowserComponent will intercept this request to obtain the access token upon successful login. You can use any URL you like here, but it must match the value you give to `GoogleConnect.setRedirectURL()` in <>. +. In the "Authorized redirect URIs" section, you will need to enter the URL to the page that the user will be sent to after a successful login. This page will appear in the simulator for a split second, as Codename One's BrowserComponent will intercept this request to get the access token upon successful login. You can use any URL you like here, but it must match the value you give to `GoogleConnect.setRedirectURL()` in <>. + image::img/google-sign-in-oauth-setup-redirect-url.png[Redirect URL,scaledwidth=30%] [[javascript-setup]] -==== Javascript Setup Instructions +==== Javascript setup instructions The Javascript port can use the same OAuth2.0 credentials as the simulator does. It doesn't require your Client Secret or redirect URL. It requires your Client ID, which you can specify using the `GoogleConnect.setClientID()` method. @@ -1441,7 +1441,7 @@ if(!gc.isUserLoggedIn()){ NOTE: The client ID and client secret values here are the ones from your <>. -IMPORTANT: The *Client ID* and *Client Secret* values are used on both the Simulator and on Android. On simulator these values are required for login to work at all. On Android these values are required to obtain an access token to query the Google API further using its various REST APIs. If you don't include these values on Android, login will still work, but `gc.getAccessToken().getToken()` will return `null`. +IMPORTANT: The *Client ID* and *Client Secret* values are used on both the Simulator and on Android. On simulator these values are required for login to work at all. On Android these values are required to get an access token to query the Google API further using its various REST APIs. If you don't include these values on Android, login will still work, but `gc.getAccessToken().getToken()` will return `null`. [[lead-component-section]] @@ -1451,14 +1451,14 @@ Codename One has two basic ways to create new components: 1. Subclass a `Component` override `paint`, implement event callbacks etc. -2. Compose many components into a new component, usually by subclassing a `Container`. +2. Compose many components into a new component, by subclassing a `Container`. Components such as https://www.codenameone.com/javadoc/com/codename1/ui/Tabs.html[Tabs] subclass `Container` which make a lot of sense for that component since it's physically a `Container`. For example, components like https://www.codenameone.com/javadoc/com/codename1/components/MultiButton.html[MultiButton], https://www.codenameone.com/javadoc/com/codename1/components/SpanButton.html[SpanButton] & https://www.codenameone.com/javadoc/com/codename1/components/SpanLabel.html[SpanLabel] don't necessarily seem like the right candidate for compositing but they are all `Container` subclasses. -Using a `Container` provides you a lot of flexibility in terms of layout & functionality for a specific component. `MultiButton` +Using a `Container` provides you a lot of flexibility in layout & functionality for a specific component. `MultiButton` is a great example of that. it's a `Container` internally that's composed of 5 labels and a `Button`. Codename One makes the `MultiButton` "feel" like a single button thru the use of `setLeadComponent(Component)` which @@ -1466,7 +1466,7 @@ turns the button into the "leader" of the component. When a `Container` hierarchy is placed under a leader all events within the hierarchy are sent to the leader, so if a label within the lead component receives a pointer pressed event this event will be sent to the leader. -For example: in the case of the `MultiButton` the internal button will receive that event and send the action performed event, change the state etc. +For example: for the `MultiButton` the internal button will receive that event and send the action performed event, change the state etc. This creates some potential issues for instance in `MultiButton`: @@ -1482,13 +1482,13 @@ myMultiButton.addActionListener((e) -> { }); ---- -The leader also determines the style state, so all the elements being lead are in the same state. For example: if the the button is pressed all elements will display their pressed states, notice that they will do so with their own styles but +The leader also determines the style state, so all the elements being lead are in the same state. For example: if the button is pressed all elements will display their pressed states, notice that they will do so with their own styles but they will each pick the pressed version of that style so a `Label` UIID within a lead component in the pressed state would return the Pressed state for a `Label` not for the `Button`. This is convenient when you need to construct more elaborate UI's and the cool thing about it's that you can do this entirely in the designer which allows assembling containers and defining the lead component inside the hierarchy. -For example: the `SpanButton` class is similar to this code: +For example: the `SpanButton` class is like this code: [source,java] ---- @@ -1537,7 +1537,7 @@ public class SpanButton extends Container { } ---- -==== Blocking Lead Behavior +==== Blocking lead behavior The `Component` class has two methods that allow you to exclude a component from lead behavior: `setBlockLead(boolean)` & `isBlockLead()`. @@ -1575,12 +1575,12 @@ void addEntry(Accordion accr) { } ---- -This allows you to add/edit entries but it also allows the delete button above to actually work separately. Without a call to `setBlockLead(true)` the delete button would cat as the rest of the accordion title. +This allows you to add/edit entries but it also allows the delete button above to actually work. Without a call to `setBlockLead(true)` the delete button would cat as the rest of the accordion title. .Accordion with delete button entries that work despite the surrounding lead image::img/lead-component-blocking.png[Accordion with delete button entries that work despite the surrounding lead,scaledwidth=20%] -=== Pull To Refresh +=== Pull to refresh Pull to refresh is the common UI paradigm that Twitter popularized where the user can pull down the form/container to receive an update. Adding this to Codename One couldn’t be simpler! @@ -1600,7 +1600,7 @@ hi.show(); .Pull to refresh demo image::img/pull-to-refresh.png[Simple pull to refresh demo,scaledwidth=20%] -=== Running 3rd Party Apps Using Display's execute +=== Running 3rd party apps using display's execute The https://www.codenameone.com/javadoc/com/codename1/ui/Display.html[Display] class's `execute` method allows you to invoke a URL which is bound to a particular application. @@ -1618,7 +1618,7 @@ allows you to support 3 result states: . `null` - you've no idea whether the URL will work on this platform. -The sample below launches a "godfather" search on IMDB when this is sure to work ( on iOS currently). You can actually try to search in the case of null as well but this sample plays it safe by using the http link which is sure to work: +The sample below launches a "godfather" search on IMDB when this is sure to work ( on iOS currently). You can actually try to search for null as well but this sample plays it safe by using the http link which is sure to work: [source,java] ---- @@ -1630,11 +1630,11 @@ if(can != null && can) { } ---- -=== Automatic Build Hint Configuration +=== Automatic build hint configuration -You try to make Codename One "seamless", this expresses itself in small details such as the automatic detection of permissions on Android etc. The build servers go a long way in setting up the environment as intuitive. But it isn't enough, build hints are often confusing and obscure. it's hard to abstract the mess that's native mobile OS's and the odd policies from Apple/Google... +You try to make Codename One "seamless," this expresses itself in small details such as the automatic detection of permissions on Android etc. The build servers go a long way in setting up the environment as intuitive. However, it isn't enough, build hints are often confusing and obscure. it's hard to abstract the mess that's native mobile OS's and the odd policies from Apple/Google... -A good example for a common problem developers face is location code that doesn't work in iOS. This is due to the `ios.locationUsageDescription` build hint that's required. The reason that build hint was added is a requirement by Apple to provide a description for every app that uses the location service. +A good example for a common problem developers face is location code that doesn't work in iOS. This is due to the `ios.locationUsageDescription` build hint that's required. The reason that build hint was added is a need by Apple to provide a description for every app that uses the location service. To solve this sort of used case you've two API's in `Display`: @@ -1659,20 +1659,20 @@ public void setProjectBuildHint(String key, String value) {} Both of these allow you to detect if a build hint is set and if not (or if it's set incorrectly) set its value... -So if you will use the location API from the simulator and you didn't define `ios.locationUsageDescription` Codename One will implicitly define a string there. The cool thing is that you will now see that string in your settings and you would be able to customize it easily. +if you will use the location API from the simulator and you didn't define `ios.locationUsageDescription` Codename One will implicitly define a string there. The cool thing is that you will now see that string in your settings and you would be able to customize it. For example, this gets way better than that trivial example! -The real value is for 3rd party cn1lib authors. For example: Google Maps or Parse. They can inspect the build hints in the simulator and show an error in case of a misconfiguration. They can even show a setup UI. Demos that need special keys in place can force the developer to set them up properly before continuing. +The real value is for 3rd party cn1lib authors. For example: Google Maps or Parse. They can inspect the build hints in the simulator and show an error in case of a misconfiguration. They can even show a setup UI. Demos that need special keys in place can force the developer to set them up before continuing. -=== Easy Thread +=== Easy thread -Working with threads is usually ranked as one of the least intuitive and painful tasks in programming. This is such an error prone task that some platforms/languages took the route of avoiding threads entirely. I needed to convert some code to work on a separate thread but I still wanted the ability to communicate and transfer data from that thread. +Working with threads is ranked as one of the least intuitive and painful tasks in programming. This is such an error prone task that some platforms/languages took the route of avoiding threads entirely. I needed to convert some code to work on a separate thread but I still wanted the ability to communicate and transfer data from that thread. -This is possible in Java but non-trivial, the thing is that this is relatively easy to do in Codename One with tools such as `callSerially` I can let arbitrary code run on the EDT. Why not offer that to any random thread? +This is possible in Java but non-trivial, the thing is that this is easy to do in Codename One with tools such as `callSerially` I can let arbitrary code run on the EDT. Why not offer that to any random thread? -that's why I created `EasyThread` which takes some of the concepts of Codeame One's threading and makes them more accessible to an arbitrary thread. This way you can move things like resource loading into a separate thread and easily synchronize the data back into the EDT as needed... +that's why I created `EasyThread` which takes some concepts of Codeame One's threading and makes them more accessible to an arbitrary thread. This way you can move things like resource loading into a separate thread and synchronize the data back into the EDT as needed... Easy thread can be created like this: @@ -1688,14 +1688,14 @@ You can send a task to the thread using: e.run(() -> doThisOnTheThread()); ---- -But it gets better, say you want to return a value: +However, it gets better, say you want to return a value: [source,java] ---- e.run((success) -> success.onSuccess(doThisOnTheThread()), (myResult) -> onEDTGotResult(myRsult)); ---- -Lets break that down... You ran the thread with the success callback on the new thread then the callback got invoked on the EDT as a result. So this code `(success) -> success.onSuccess(doThisOnTheThread())` ran off the EDT in the thread and when you invoked the `onSuccess` callback it sent it asynchronously to the EDT here: `(myResult) -> onEDTGotResult(myRsult)`. +Lets break that down... You ran the thread with the success callback on the new thread then the callback got invoked on the EDT as a result. this code `(success) -> success.onSuccess(doThisOnTheThread())` ran off the EDT in the thread and when you invoked the `onSuccess` callback it sent it asynchronously to the EDT here: `(myResult) -> onEDTGotResult(myRsult)`. These asynchronous calls make things a bit painful to wade thru so instead I chose to wrap them in a simplified synchronous version: @@ -1708,11 +1708,11 @@ int result = e.run(() -> { }); ---- -There are a few other variants like `runAndWait` and there is a `kill()` method which stops a thread and releases its resources. +a few other variants like `runAndWait` and there is a `kill()` method which stops a thread and releases its resources. -=== Mouse Cursor +=== Mouse cursor -Codename one can change the mouse cursor when hovering over specific areas to indicate resizability, movability etc. For obvious reasons this feature is available in the desktop and JavaScript ports as the other ports rely mostly on touch interaction. The feature is off by default and needs to be enabled on a `Form` by using `Form.setEnableCursors(true);`. If you're writing a custom component that can use cursors such as `SplitPane` you can use: +Codename one can change the mouse cursor when hovering over specific areas to show resizability, movability etc. For obvious reasons this feature is available in the desktop and JavaScript ports as the other ports rely on touch interaction. The feature is off by default and needs to be enabled on a `Form` by using `Form.setEnableCursors(true);`. If you're writing a custom component that can use cursors such as `SplitPane` you can use: [source,java] ---- @@ -1726,11 +1726,11 @@ protected void initComponent() { Once this is enabled you can set the cursor over a specific region using `cmp.setCursor()` which accepts one of the cursor constants defined in `Component`. -=== Working With GIT +=== Working with GIT -Working with GIT for storing Codename One projects isn't exactly a feature but since it's so ubiquitous you think it's important to have a common guideline. +Working with GIT for storing Codename One projects isn't a feature but since it's so ubiquitous you think it's important to have a common guideline. -When you first started committing to git you used something like this for netbeans projects: +When you first started committing to git you used something like this for NetBeans projects: ---- @@ -1745,9 +1745,9 @@ Removing the jars, build, private folder etc. makes a lot of sense but there are ==== cn1lib's -You will notice you excluded the jars which are stored under lib and you exclude the Codename One source zip. But I didn't exclude cn1libs... That was an omission since the original project you committed didn't have cn1libs. But should you commit a binary file to git? +You will notice you excluded the jars which are stored under lib and you exclude the Codename One source zip. However, I didn't exclude cn1libs... That was an omission since the original project you committed didn't have cn1libs. However, should you commit a binary file to git? -I don't know. Generally git isn't good with binaries but cn1libs make sense. In another project that did have a cn1lib I did this: +I don't know. Git isn't good with binaries but cn1libs make sense. In another project that did have a cn1lib I did this: ---- @@ -1762,22 +1762,22 @@ native/internal_tmp/ The important lines are `lib/impl/` and `native/internal_tmp/`. Technically cn1libs are zips. When you do a refresh libs they unzip into the right directories under `lib/impl` and `native/internal_tmp`. By excluding these directories you can remove duplicates that can result in conflicts. -==== Resource Files +==== Resource files Committing the res file is a matter of personal choice. it's committed in the git ignore files above but you can remove it. The res file is at risk of corruption and in that case having a history you can refer to, matters a lot. -But the resource file is a bit of a problematic file. As a binary file if you've a team working with it the conflicts can be a major blocker. This was far worse with the old GUI builder, that was one of the big motivations of moving into the new GUI builder which works better for teams. +However, the resource file is a bit of a problematic file. As a binary file if you've a team working with it the conflicts can be a major blocker. This was far worse with the old GUI builder, that was one of the big motivations of moving into the new GUI builder which works better for teams. Still, if you want to keep an eye of every change in the resource file you can switch on the #File# -> #XML Team Mode# which should be on by default. This mode creates a file hierarchy under the `res` directory to match the res file you opened. For example: if you've a file named `src/theme.res` it will create a matching `res/theme.xml` and also nest all the images and resources you use in the res directory. that's useful as you can edit the files directly and keep track of every file in git. For example, this has two big drawbacks: - it's flaky - while this mode works it never reached the stability of the regular res file mode -- It conflicts - the simulator/device are oblivious to this mode. So if you fetch an update you also need to update the res file and you might still have conflicts related to that file +- It conflicts - the simulator/device are oblivious to this mode. if you fetch an update you also need to update the res file and you might still have conflicts related to that file -Ultimately both of these issues shouldn't be a deal breaker. Even though this mode is a bit flaky it's better than the alternative as you can literally "see" the content of the resource file. You can easily revert and reapply your changes to the res file when merging from git, it's tedious but again not a deal breaker. +Both of these issues shouldn't be a deal breaker. Even though this mode is a bit flaky it's better than the alternative as you can "see" the content of the resource file. You can revert and reapply your changes to the res file when merging from git, it's tedious but again not a deal breaker. -==== Eclipse Version +==== Eclipse version Building on the gitignore you've for NetBeans the eclipse version should look like this: diff --git a/docs/developer-guide/Monetization.asciidoc b/docs/developer-guide/Monetization.asciidoc index c0afbfc3af..39b96ac0ee 100644 --- a/docs/developer-guide/Monetization.asciidoc +++ b/docs/developer-guide/Monetization.asciidoc @@ -4,9 +4,9 @@ Codename One integrates many built-in monetization options such as ad network su Many monetization options are available as third-party https://www.codenameone.com/cn1libs.html[cn1libs] that you can install through the Codename One website. -=== Google Play ads +=== Google play ads -The simplest banner-ad support is often the easiest network to use. To enable mobile ads, create an ad unit in AdMob's website. You should end up with a key similar to this: +The simplest banner-ad support is often the easiest network to use. To enable mobile ads, create an ad unit in AdMob's website. You should end up with a key like this: `ca-app-pub-8610616152754010/3413603324` @@ -19,26 +19,26 @@ There is a special ad unit ID for test ads. If you specify `ca-app-pub-394025609 === In-app purchase -In-app purchase is a helpful way to make app development profitable. Codename One supports in-app purchases of consumable and non-consumable products on Android and iOS. It also supports subscriptions. Even though the concept is simple, in-app purchase involves many moving parts, especially for subscriptions. +in-app purchase is a helpful way to make app development profitable. Codename One supports in-app purchases of consumable and non-consumable products on Android and iOS. It also supports subscriptions. Even though the concept is simple, in-app purchase involves many moving parts, for subscriptions. ==== The SKU -In-app purchase support centers around the set of SKUs you want to sell. Each product, whether it's a one-month subscription, an upgrade to the "Pro" version, or "10 disco credits", has a SKU (stock-keeping unit). Ideally, you can use the same SKU across every store that sells your app. +in-app purchase support centers around the set of SKUs you want to sell. Each product, whether it's a one-month subscription, an upgrade to the "Pro" version, or "10 disco credits," has a SKU (stock-keeping unit). Ideally, you can use the same SKU across every store that sells your app. -==== Types of Products +==== Types of products -There are generally 4 classifications for products: +There are 4 classifications for products: -1. **Non-consumable Product** - This is a product that the user purchases once to "own". They can't re-purchase it. One example is a product that upgrades your app to a "Pro" version. +1. **Non-consumable Product** - This is a product that the user purchases once to "own." They can't re-buy it. One example is a product that upgrades your app to a "Pro" version. 2. **Consumable Product** - This is a product that the user can buy more than once. For example, you might have a product for "10 Credits" that lets the user buy items in a game. 3. **Non-Renewable Subscription** - A subscription that you buy once, and won't be "auto-renewed" by the app store. These are almost identical to consumable products, except that subscriptions need to be transferable across all the user's devices. This means that non-renewable subscriptions require that you've a server that keeps track of the subscriptions. 4. **Renewable Subscriptions** - A subscription that the app store manages. The user will be automatically billed when the subscription period ends, and the subscription will renew. NOTE: These subscription categories may not be explicitly supported by a given store, or they may use different names. You can integrate each product type in a cross-platform way using Codename One. For example, Google Play doesn't distinguish between consumable products and non-renewable subscriptions, but iTunes does. -==== The "Hello World" of in-app purchase +==== The "hello world" of in-app purchase -Start with a simple example of an app that sells "Worlds". First, pick the SKU for the product. Here you use `com.codename1.world`. +Start with a simple example of an app that sells `Worlds`. First, pick the SKU for the product. Here you use `com.codename1.world`. [source,java] ---- @@ -119,28 +119,28 @@ At this point, the app can track the sale of the world. To make it more useful, ---- -NOTE: You can test out this code in the simulator without doing any additional setup and it will work. If you want the code to work properly on Android and iOS, you'll need to set up the app and in-app purchase settings in the Google Play and iTunes stores respectively as explained below +NOTE: You can test out this code in the simulator without doing any more setup and it will work. If you want the code to work on Android and iOS, you'll need to set up the app and in-app purchase settings in the Google Play and iTunes stores respectively as explained below -When the app first opens we see our button: +When the app first opens you see your button: -.In-app purchase demo app -image::img/iap-demo-1.png[In-app purchase demo app,scaledwidth=20%] +.in-app purchase demo app +image::img/iap-demo-1.png[in-app purchase demo app,scaledwidth=20%] In the simulator, clicking on the "Buy World" button will bring up a prompt to ask you if you want to approve the purchase. .Approving the purchase in the simulator image::img/iap-demo2.png[Approving the purchase in the simulator,scaledwidth=30%] -Now if I try to buy the product again, it pops up the dialog to let me know that I already own it. +Now if I try to buy the product again, it pops up the dialog to let's know that I already own it. -.In App purchase already owned -image::img/iap-demo3.png[In App purchase already owned,scaledwidth=20%] +.In App buy already owned +image::img/iap-demo3.png[In App buy already owned,scaledwidth=20%] -==== Making it Consumable +==== Making it consumable -In the "Buy World" example above, the "world" product was non-consumable, since we could only buy the world once. We could change it to a consumable product by disregarding whether it was purchased before & keeping track of how many times it had been purchased. +In the "Buy World" example above, the "world" product was non-consumable, since you could only buy the world once. You could change it to a consumable product by disregarding whether it was purchased before & keeping track of how many times it had been purchased. -We'll use storage to keep track of the number of worlds that the user purchased. We need two methods to manage this count. One method gets the number of worlds that we own, and another adds a world to this count. +You'll use storage to keep track of the number of worlds that the user purchased. You need two methods to manage this count. One method gets the number of worlds that you own, and another adds a world to this count. [source,java] ---- @@ -169,7 +169,7 @@ public void addWorld() { } ---- -Now we'll change our purchase code as follows: +Now you'll change your buy code as follows: [source,java] ---- @@ -181,7 +181,7 @@ buyWorld.addActionListener(e->{ }); ---- -And our `itemPurchased()` callback will need to add a world: +And your `itemPurchased()` callback will need to add a world: [source,java] ---- @@ -192,24 +192,24 @@ public void itemPurchased(String sku) { } ---- -NOTE: When we set up the products in the iTunes store we will need to mark the product as a consumable product or iTunes will prevent us from purchasing it more than once +NOTE: When you set up the products in the iTunes store you will need to mark the product as a consumable product or iTunes will prevent you from purchasing it more than once -==== Non-Renewable Subscriptions +==== Non-Renewable subscriptions -As we discussed before, there are two types of subscriptions: +As you discussed before, there are two types of subscriptions: . Non-renewable . Auto-renewable -Non-renewable subscriptions are the same as consumable products, except that they are shareable across devices. Auto-renewable subscriptions will continue as long as the user doesn't cancel the subscription. They will be re-billed automatically by the appropriate app-store when the chosen period expires, and the app-store handles the management details itself. +Non-renewable subscriptions are the same as consumable products, except that they are shareable across devices. Auto-renewable subscriptions will continue as long as the user doesn't cancel the subscription. They will be re-billed automatically by the appropriate app-store when the chosen period expires, and the app-store handles the management details itself. -NOTE: The concept of an "Non-renewable" subscription is unique to iTunes. Google Play has no formal similar option. In order to create a non-renewable subscription SKU that behaves the same in your iOS and Android apps you would create it as a *regular product* in Google play, and a Non-renewable subscription in the iTunes store. We'll learn more about that in a later post when we go into the specifics of app store setup. +NOTE: The concept of an "Non-renewable" subscription is unique to iTunes. Google Play has no formal similar option. To create a non-renewable subscription SKU that behaves the same in your iOS and Android apps you would create it as a *regular product* in Google play, and a Non-renewable subscription in the iTunes store. You'll learn more about that in a later post when you go into the specifics of app store setup. -IMPORTANT: The `Purchase` class includes both a `purchase()` method and a `subscribe()` method. On some platforms it makes no difference which one you use, but on Android it matters. If the product is set up as a subscription in Google Play, then you *must* use `subscribe()` to purchase the product. If it is set up as a regular product, then you *must* use `purchase()`. Since we enter "Non-renewable" subscriptions as regular products in the play store, we would use the `purchase()` method. +IMPORTANT: The `Purchase` class includes both a `purchase()` method and a `subscribe()` method. On some platforms it makes no difference which one you use, but on Android it matters. If the product is set up as a subscription in Google Play, then you *must* use `subscribe()` to buy the product. If it's set up as a regular product, then you *must* use `purchase()`. Since you enter "Non-renewable" subscriptions as regular products in the play store, you would use the `purchase()` method. ==== Promotional offers (iOS) -Apple allows you to present discounted introductory pricing to existing subscribers via https://developer.apple.com/documentation/storekit/skpaymentdiscount[promotional offers]. Codename One surfaces this capability through overloads of both `Purchase.purchase(String, PromotionalOffer)` and `Purchase.subscribe(String, PromotionalOffer)`, which forward the promotional context to StoreKit when you initiate the transaction. Promotional offers are only honoured by iOS, so the overloads simply fall back to the regular purchase flow on other platforms. +Apple allows you to present discounted introductory pricing to existing subscribers via https://developer.apple.com/documentation/storekit/skpaymentdiscount[promotional offers]. Codename One surfaces this capability through overloads of both `Purchase.purchase(String, PromotionalOffer)` and `Purchase.subscribe(String, PromotionalOffer)`, which forward the promotional context to StoreKit when you start the transaction. Promotional offers are only honoured by iOS, so the overloads fall back to the regular buy flow on other platforms. To build the signed discount payload required by Apple you can use the `ApplePromotionalOffer` helper: @@ -226,48 +226,48 @@ Purchase purchase = Purchase.getInAppPurchase(); purchase.subscribe(SKU_WORLD_MONTHLY, offer); ---- -Apple generates the signature and timestamp from your App Store Connect server notifications endpoint; Codename One simply passes them to the native StoreKit APIs. For one-time products you can call `purchase(sku, offer)` instead of `subscribe(...)`. +Apple generates the signature and timestamp from your App Store Connect server notifications endpoint; Codename One passes them to the native StoreKit APIs. For one-time products you can call `purchase(sku, offer)` instead of `subscribe(...)`. ==== Restoring purchases and managing subscriptions -Both Apple and Google provide built-in user interfaces for restoring past purchases and managing subscription billing preferences. Codename One exposes these entry points so you can surface the native flows without reimplementing them yourself. +Both Apple and Google provide built-in user interfaces for restoring past purchases and managing subscription billing preferences. Codename One exposes these entry points so you can surface the native flows without reimplementing them yourself. -* Restores: Call `Purchase.isRestoreSupported()` before presenting a "Restore purchases" button. When supported (iOS currently implements this natively), invoke `Purchase.restore()` to prompt the operating system to re-deliver past transactions. Your app should implement `RestoreCallback` (similar to how you implement `PurchaseCallback`) so you can respond to individual `itemRestored(...)` events and to the completion or failure of the restore request. -* Subscription management: Use `Purchase.isManageSubscriptionsSupported()` to detect whether the platform can show the subscription management UI. When it returns `true`, calling `Purchase.manageSubscriptions(null)` opens the store-specific settings screen (Apple's subscription center on iOS and Google Play's subscription management activity on Android). On Android you can optionally pass a SKU to deep-link directly to the plan the user should manage. +* Restores: Call `Purchase.isRestoreSupported()` before presenting a "Restore purchases" button. When supported (iOS implements this natively), invoke `Purchase.restore()` to prompt the operating system to re-deliver past transactions. Your app should implement `RestoreCallback` (like how you implement `PurchaseCallback`) so you can respond to individual `itemRestored(...)` events and to the completion or failure of the restore request. +* Subscription management: Use `Purchase.isManageSubscriptionsSupported()` to detect whether the platform can show the subscription management UI. When it returns `true`, calling `Purchase.manageSubscriptions(null)` opens the store-specific settings screen (Apple's subscription center on iOS and Google Play's subscription management activity on Android). On Android you can optionally pass a SKU to deep-link directly to the plan the user should manage. -Because these flows are handled by the underlying store your UI doesn't need to rebuild any billing screens. Simply gate the buttons on the capability checks above so that iOS and Android users get the familiar restore/manage dialogs while other platforms can fall back to your own help copy. +Because these flows are handled by the underlying store your UI doesn't need to rebuild any billing screens. Gate the buttons on the capability checks above so that iOS and Android users get the familiar restore/manage dialogs while other platforms can fall back to your own help copy. ==== The Server-Side -Since a subscription purchased on one user device *needs* to be available across the user's devices (Apple's rules for non-renewable subscriptions), our app will need to have a server-component. In this section, we'll gloss over that & "mock" the server interface. We'll go into the specifics of the server-side below. +Since a subscription purchased on one user device *needs* to be available across the user's devices (Apple's rules for non-renewable subscriptions), your app will need to have a server-component. In this section, you'll gloss over that & "mock" the server interface. You'll go into the specifics of the server-side below. -===== The Receipts API +===== The receipts API -Subscriptions, in Codename One use the "Receipts" API. It's up to you to register a receipt store with the In-App purchase instance, which allows Codename one to load receipts (from your server), and submit new receipts to your server. A `Receipt` includes information such as: +Subscriptions, in Codename One use the "Receipts" API. It's up to you to register a receipt store with the in-app purchase instance, which allows Codename one to load receipts (from your server), and submit new receipts to your server. A `Receipt` includes information such as: . Store code (since you may be dealing with receipts from itunes, google play & Microsoft) . SKU . Transaction ID (store specific) . Expiry Date . Cancellation date -. Purchase date +. Buy date . Order Data (that you can use on the server-side to verify the receipt and load receipt details directly from the store it originated from). The `Purchase` provides a set of methods for interacting with the receipt store, such as: -. `isSubscribed([skus])` - Checks to see if the user is currently subscribed to any of the provided skus. +. `isSubscribed([skus])` - Checks to see if the user is subscribed to any of the provided skus. . `getExpiryDate([skus])` - Checks the expiry date for a set of skus. -. `synchronizeReceipts()` - Synchronizes the receipts with the receipt store. This will attempt to submit any pending purchase receipts to the receipt store, and the reload receipts from the receipt store. +. `synchronizeReceipts()` - Synchronizes the receipts with the receipt store. This will try to submit any pending purchase receipts to the receipt store, and the reload receipts from the receipt store. -In order for any of this to work, you must implement the `ReceiptStore` interface, and register it with the Purchase instance. Your receipt store must implement two methods: +In order for any of this to work, you must implement the `ReceiptStore` interface, and register it with the Buy instance. Your receipt store must implement two methods: -. `fetchReceipts(SuccessCallback callback)` - Loads all of the receipts from your receipt store for the current user. -. `submitReceipt(Receipt receipt, SuccessCallback callback)` - Submits a receipt to your receipt store. This gives you an opportunity to add details to the receipt such as an expiry date. +. `fetchReceipts(SuccessCallback callback)` - Loads all the receipts from your receipt store for the current user. +. `submitReceipt(Receipt receipt, SuccessCallback callback)` - Submits a receipt to your receipt store. This gives you an opportunity to add details to the receipt such as an expiry date. -==== The "Hello World" of Non-Renewable Subscriptions +==== The "hello world" of Non-Renewable subscriptions -We'll expand on the theme of "Buying" the world for this app, except, this time we will "Rent" the world for a period of time. We'll have two products: +You'll expand on the theme of "Buying" the world for this app, except, this time you will "Rent" the world for a period of time. You'll have two products: . A 1 month subscription . A 1 year subscription @@ -283,15 +283,15 @@ public static final String[] PRODUCTS = { }; ---- -Notice that we create two separate SKUs for the 1 month and 1 year subscription. **Each subscription period must have its own SKU**. I have created an array (`PRODUCTS`) that contains both of the SKUs. This is handy, as you'll see in the examples ahead, because the APIs for checking status and expiry date of a subscription take the SKUs in a "subscription group" as input. +Notice that you create two separate SKUs for the 1 month and 1 year subscription. **Each subscription period must have its own SKU**. I have created an array (`PRODUCTS`) that contains both of the SKUs. This is handy, as you'll see in the examples ahead, because the APIs for checking status and expiry date of a subscription take the SKUs in a "subscription group" as input. -NOTE: Different SKUs that sell the same service/product but for different periods form a "subscription group". Conceptually, customers are not subscribing to a particular SKU, they are subscribing to the subscription group of which that SKU is a member. As an example, if a user purchases a 1 month subscription to "the world", they are actually subscribing to "the world" subscription group. +NOTE: Different SKUs that sell the same service/product but for different periods form a "subscription group." Conceptually, customers aren't subscribing to a particular SKU, they are subscribing to the subscription group of which that SKU is a member. As an example, if a user purchases a 1 month subscription to "the world," they are actually subscribing to "the world" subscription group. -It's up to you to know the grouping of your SKUs. Any methods in the `Purchase` class that check subscription status or expiry date of a SKU should be passed *all* SKUs of that subscription group. E.g. If you want to know if the user is subscribed to the `SKU_WORLD_1_MONTH` subscription, it would not be sufficient to call `iap.isSubscribed(SKU_WORLD_1_MONTH)`, because that wouldn't take into account if the user had purchased a 1 year subscription. The correct way is to always call `iap.isSubscribed(SKU_WORLD_1_MONTH, SKU_WORLD_1_YEAR)`, or simply `iap.isSubscribed(PRODUCTS)` since we have placed both SKUs into our PRODUCTS array. +It's up to you to know the grouping of your SKUs. Any methods in the `Purchase` class that check subscription status or expiry date of a SKU should be passed *all* SKUs of that subscription group. For example, If you want to know if the user is subscribed to the `SKU_WORLD_1_MONTH` subscription, it would not be enough to call `iap.isSubscribed(SKU_WORLD_1_MONTH)`, because that wouldn't consider if the user had purchased a 1 year subscription. The correct way is to always call `iap.isSubscribed(SKU_WORLD_1_MONTH, SKU_WORLD_1_YEAR)`, or `iap.isSubscribed(PRODUCTS)` since you have placed both SKUs into your PRODUCTS array. -===== Implementing the Receipt Store +===== Implementing the receipt store -NOTE: The receipt store is intended to interface with a server so that the subscriptions can be synced with multiple devices, as required by Apple's guidelines. For this post we'll just store our receipts on device using internal storage. Moving the logic to a server is a simple matter that we will cover in a future post when we cover the server-side. +NOTE: The receipt store is intended to interface with a server so that the subscriptions can be synced with multiple devices, as required by Apple's guidelines. For this post you'll just store your receipts on device using internal storage. Moving the logic to a server is a simple matter that this guide covers in a future post when you cover the server-side. .The Receipt store is a layer between your server and Codename One @@ -303,7 +303,7 @@ A basic receipt store needs to implement just two methods: . `fetchReceipts` . `submitReceipt` -Generally we'll register it in our app's init() method so that it's always available. +You'll register it in your app's init() method so that it's always available. [source,java] ---- @@ -325,9 +325,9 @@ public void init(Object context) { } ---- -These methods are designed to be asynchronous since real-world apps will always be connecting to some sort of network service. Therefore, instead of returning a value, both of these methods are passed instances of the `SuccessCallback` class. It's important to make sure to call `callback.onSuccess()` *ALWAYS* when the methods have completed, even if there is an error, or the Purchase class will just assume that you're taking a long time to complete the task, and will continue to wait for you to finish. +These methods are designed to be asynchronous since real-world apps will always be connecting to some sort of network service. So, instead of returning a value, both of these methods are passed instances of the `SuccessCallback` class. It's important to make sure to call `callback.onSuccess()` *ALWAYS* when the methods have completed, even if there is an error, or the Buy class will just assume that you're taking a long time to complete the task, and will continue to wait for you to finish. -Once implemented, our `fetchReceipts()` method will look like: +Once implemented, your `fetchReceipts()` method will look like: [source,java] ---- @@ -353,11 +353,11 @@ public void fetchReceipts(SuccessCallback callback) { } ---- -This is fairly straight forward. We're checking to see if we already have a list of receipts stored. If so we return that list to the callback. If not we return an empty array of receipts. +This is straight forward. You're checking to see if you already have a list of receipts stored. If so you return that list to the callback. If not you return an empty array of receipts. NOTE: `Receipt` implements `Externalizable` so you are able to write instances directly to Storage. -The `submitReceipt()` method is a little more complex, as it needs to calculate the new expiry date for our subscription. +The `submitReceipt()` method is a little more complex, as it needs to calculate the new expiry date for your subscription. [source,java] ---- @@ -423,24 +423,24 @@ public void submitReceipt(Receipt receipt, SuccessCallback callback) { } ---- -The main logic of this method involves iterating through all of the existing receipts to find the *latest* current expiry date, so that when the user purchases a subscription, it's added onto the end of the current subscription (if one exists) rather than going from today's date. This enables users to safely renew their subscription before the subscription has expired. +The main logic of this method involves iterating through all the existing receipts to find the *latest* current expiry date, so that when the user purchases a subscription, it's added onto the end of the current subscription (if one exists) rather than going from today's date. This enables users to safely renew their subscription before the subscription has expired. -In the real-world, we would implement this logic on the server-side. +In the real-world, you would implement this logic on the server-side. -NOTE: The iTunes store and Play store have no knowledge of your subscription durations. This is why it's up to you to set the expiry date in the `submitReceipt` method. Non-renewable subscriptions are essentially no different than regular consumable products. It's up to you to manage the subscription logic - and Apple, in particular, requires you to do so using a server. +NOTE: The iTunes store and Play store have no knowledge of your subscription durations. This is why it's up to you to set the expiry date in the `submitReceipt` method. Non-renewable subscriptions are essentially no different than regular consumable products. It's up to you to manage the subscription logic - and Apple, in particular, requires you to do so using a server. -===== Synchronizing Receipts +===== Synchronizing receipts -In order for your app to provide you with current data about the user's subscriptions and expiry dates, you need to synchronize the receipts with your receipt store. `Purchase` provides a set of methods for doing this. Generally I'll call one of them inside the `start()` method, and I may resynchronize at other strategic times if I suspect that the information may have changed. +In order for your app to provide you with current data about the user's subscriptions and expiry dates, you need to synchronize the receipts with your receipt store. `Purchase` provides a set of methods for doing this. You'll call one of them inside the `start()` method, and I may resynchronize at other strategic times if I suspect that the information may have changed. The following methods can be used for synchronization: -. `synchronizeReceipts()` - Asynchronously synchronizes receipts in the background. You won't be notified when it's complete. -. `synchronizeReceiptsSync()` - Synchronously synchronizes receipts, and blocks until it's complete. This is safe to use on the EDT as it employs `invokeAndBlock` under the covers. -. `synchronizeReceipts(final long ifOlderThanMs, final SuccessCallback callback)` - Asynchronously synchronizes receipts, but only if they haven't been synchronized in the specified time period. E.g. In your start() method you might decide that you only want to synchronize receipts once per day. This also includes a callback that will be called when synchronization is complete. +. `synchronizeReceipts()` - Asynchronously synchronizes receipts in the background. You won't be notified when it's complete. +. `synchronizeReceiptsSync()` - Synchronously synchronizes receipts, and blocks until it's complete. This is safe to use on the EDT as it employs `invokeAndBlock` under the covers. +. `synchronizeReceipts(final long ifOlderThanMs, final SuccessCallback callback)` - Asynchronously synchronizes receipts, but only if they haven't been synchronized in the specified time. For example, In your start() method you might decide that you only want to synchronize receipts once per day. This also includes a callback that will be called when synchronization is complete. . `synchronizeReceiptsSync(long ifOlderThanMs)` - A synchronous version that will only refetch if data is older than given time. -In our hello world app we synchronize the subscriptions in a few places. +In your hello world app you synchronize the subscriptions in a few places. At the end of the `start()` method: @@ -458,7 +458,7 @@ public void start() { } ---- -And we also provide a button to allow the user to manually synchronize the receipts. +And you also provide a button to allow the user to manually synchronize the receipts. [source,java] ---- @@ -472,17 +472,17 @@ syncReceipts.addActionListener(e->{ }); ---- -===== Expiry Dates and Subscription Status +===== Expiry dates and subscription status -Now that we have a receipt store registered, and we have synchronized our receipts, we can query the `Purchase` instance to see if a SKU or set of SKUs is currently subscribed. There are three useful methods in this realm: +Now that you have a receipt store registered, and you have synchronized your receipts, you can query the `Purchase` instance to see if a SKU or set of SKUs is subscribed. three useful methods in this realm: -. `boolean isSubscribed(String... skus)` - Checks to see if the user is currently subscribed to any of the provided SKUs. +. `boolean isSubscribed(String... skus)` - Checks to see if the user is subscribed to any of the provided SKUs. . `Date getExpiryDate(String... skus)` - Gets the latest expiry date of a set of SKUs. -. `Receipt getFirstReceiptExpiringAfter(Date dt, String... skus)` - This method will return the earliest receipt with an expiry date after the given date. This is needed in cases where you need to decide if the user should have access to some content based on its publication date. E.g. If you published an issue of your e-zine on March 1, and the user purchased a subscription on March 15th, then they should get access to the March 1st issue even though it doesn't necessarily fall in the subscription period. Being able to easily fetch the first receipt after a given date makes it easier to determine if a particular issue should be covered by a subscription. +. `Receipt getFirstReceiptExpiringAfter(Date dt, String... skus)` - This method will return the earliest receipt with an expiry date after the given date. This is needed in cases where you need to decide if the user should have access to some content based on its publication date. For example, If you published an issue of your e-zine on March 1, and the user purchased a subscription on March 15th, then they should get access to the March 1st issue even though it doesn't necessarily fall in the subscription period. Being able to fetch the first receipt after a given date makes it easier to determine if a particular issue should be covered by a subscription. -If you need to know more information about subscriptions, you can always just call `getReceipts()` to obtain a list of all of the current receipts and determine for yourself what the user should have access to. +If you need to know more information about subscriptions, you can always just call `getReceipts()` to get a list of all the current receipts and determine for yourself what the user should have access to. -In the hello world app we'll use this information in a few different places. On our main form we'll include a label to show the current expiry date, and we allow the user to press a button to synchronize receipts manually if they think the value is out of date. +In the hello world app you'll use this information in a few different places. On your main form you'll include a label to show the current expiry date, and you allow the user to press a button to synchronize receipts manually if they think the value is out of date. [source,java] ---- @@ -504,11 +504,11 @@ syncReceipts.addActionListener(e->{ }); ---- -===== Allowing the User to Purchase the Subscription +===== Allowing the user to purchase the subscription -You should now have all of the background required to implement the Hello World Subscription app. So we'll return to the code and see how the user purchases a subscription. +You should now have all the background required to implement the Hello World Subscription app. you'll return to the code and see how the user purchases a subscription. -In the main form, we want two buttons to subscribe to the "World", for one month and one year respectively. They look like: +In the main form, you want two buttons to subscribe to the `World`, for one month and one year respectively. They look like: [source,java] ---- @@ -552,23 +552,23 @@ rentWorld1Y.addActionListener(e->{ } }); ---- -<1> In the event handler we check if the user is subscribed by calling `isSubscribed(PRODUCTS)`. Notice that we check it against the array of both the one month and one year subscription SKUs. -<2> We are able to tell the user when the current expiry date is so that they can gauge whether to proceed. -<3> Since this is a non-renewable subscription, we use the `Purchase.purchase()` method. See following note about `subscribe()` vs `purchase()` +<1> In the event handler you check if the user is subscribed by calling `isSubscribed(PRODUCTS)`. Notice that you check it against the array of both the one month and one year subscription SKUs. +<2> You are able to tell the user when the current expiry date is so that they can gauge whether to proceed. +<3> Since this is a non-renewable subscription, you use the `Purchase.purchase()` method. See following note about `subscribe()` vs `purchase()` -==== subscribe() vs purchase() +==== Subscribe() vs purchase() The `Purchase` class includes two methods for initiating a purchase: 1. `purchase(sku)` 2. `subscribe(sku)` -Which one you use depends on the type of product that is being purchased. If your product is set up as a subscription in the Google Play store, then you should use `subscribe(sku)`. Otherwise, you should use `purchase(sku)`. +Which one you use depends on the type of product that's being purchased. If your product is set up as a subscription in the Google Play store, then you should use `subscribe(sku)`. Otherwise, you should use `purchase(sku)`. -===== Handling Purchase Callbacks +===== Handling purchase callbacks -The purchase callbacks are very similar to the ones that we implemented in the regular in-app purchase examples: +The buy callbacks are like the ones implemented in the regular in-app purchase examples: [source,java] ---- @@ -587,7 +587,7 @@ public void itemPurchaseError(String sku, String errorMessage) { } ---- -Notice that, in `itemPurchased()` we don't need to explicitly create any receipts or submit anything to the receipt store. This is handled for you automatically. We do make a call to `synchronizeReceiptsSync()` but this is just to ensure that our toast message has the new expiry date loaded already. +Notice that, in `itemPurchased()` you don't need to explicitly create any receipts or submit anything to the receipt store. This is handled for you automatically. You do make a call to `synchronizeReceiptsSync()` but this is just to ensure that your toast message has the new expiry date loaded already. ==== Screenshots @@ -600,59 +600,59 @@ image::img/in-app-purchase-subscription-dialog.png[Dialog shown when subscribing .Simulator confirm dialog when purchasing a subscription image::img/in-app-purchase-subscription-confirm.png[Simulator confirm dialog when purchasing a subscription,scaledwidth=40%] -.Upon successful purchase, the toastbar message is shown -image::img/in-app-purchase-subscription-toastbar-success.png[Upon successful purchase, the toastbar message is shown,scaledwidth=20%] +.Upon successful buy, the toastbar message is shown +image::img/in-app-purchase-subscription-toastbar-success.png[Upon successful buy, the toastbar message is shown,scaledwidth=20%] ==== Summary -This section demonstrated how to set up an app to use non-renewable subscriptions using in-app purchase. Non-renewable subscriptions are the same as regular consumable products except for the fact that they are shared by all of the user's devices, and thus, require a server component. The app store has no knowledge of the duration of your non-renewable subscriptions. It's up to you to specify the expiry date of purchased subscriptions on their receipts when they are submitted. Google play doesn't formally have a "non-renewable" subscription product type. To implement them in Google play, you would just set up a regular product. It's how you handle it internally that makes it a subscription, and not just a regular product. +This section demonstrated how to set up an app to use non-renewable subscriptions using in-app purchase. Non-renewable subscriptions are the same as regular consumable products except for the fact that they are shared by all the user's devices, and thus, require a server component. The app store has no knowledge of the duration of your non-renewable subscriptions. It's up to you to specify the expiry date of purchased subscriptions on their receipts when they are submitted. Google play doesn't formally have a "non-renewable" subscription product type. To implement them in Google play, you would just set up a regular product. It's how you handle it internally that makes it a subscription, and not just a regular product. -Codename One uses the `Receipt` class as the foundation for its subscriptions infrastructure. You, as the developer, are responsible for implementing the `ReceiptStore` interface to provide the receipts. The `Purchase` instance will load receipts from your ReceiptStore, and use them to determine whether the user is currently subscribed to a subscription, and when the subscription expires. +Codename One uses the `Receipt` class as the foundation for its subscriptions infrastructure. You, as the developer, are responsible for implementing the `ReceiptStore` interface to provide the receipts. The `Purchase` instance will load receipts from your ReceiptStore, and use them to determine whether the user is subscribed to a subscription, and when the subscription expires. -==== Auto-Renewable Subscriptions +==== Auto-Renewable subscriptions -Auto-renewable subscriptions provide, arguably, an easier path to recurring revenue than non-renewable subscriptions because all of the subscription stuff is handled by the app store. You defer almost entirely to the app store (iTunes for iOS, and Play for Android) for billing and management. +Auto-renewable subscriptions provide, arguably, an easier path to recurring revenue than non-renewable subscriptions because all the subscription stuff is handled by the app store. You defer almost entirely to the app store (iTunes for iOS, and Play for Android) for billing and management. If there is a down-side, it would be that you are also subject to the rules of each app store - and they take their cut of the revenue. . For more information about Apple's auto-renewable subscription features and rules see https://developer.apple.com/app-store/subscriptions/[this document]. . For more information about subscriptions in Google play, see https://developer.android.com/google/play/billing/billing_subscriptions.html[this document]. -==== Auto-Renewable vs Non-Renewable. Best Choice? +==== Auto-Renewable vs Non-Renewable. best choice -When deciding between auto-renewable and non-renewable subscriptions, as always, the answer will depend on your needs and preferences. Auto-renewables are nice because it takes the process completely out of your hands. You just get paid. On the other hand, there are valid reasons to want to use non-renewables. E.g. You can't cancel an auto-renewable subscription for a user. They have to do that themselves. You may also want more control over the subscription and renewal process, in which case a non-renewable might make more sense. +When deciding between auto-renewable and non-renewable subscriptions, as always, the answer will depend on your needs and preferences. Auto-renewables are nice because it takes the process out of your hands. You just get paid. On the other hand, there are valid reasons to want to use non-renewables. For example, You can't cancel an auto-renewable subscription for a user. They have to do that themselves. You may also want more control over the subscription and renewal process, in which case a non-renewable might make more sense. -NOTE: There are some developers https://marco.org/2013/12/02/auto-renewable-subscriptions[that are opposed to auto-renewable subscriptions], we don't have enough information to make a solid recommendation on this matter +NOTE: Some developers https://marco.org/2013/12/02/auto-renewable-subscriptions[are opposed to auto-renewable subscriptions]. There isn't enough information to make a solid recommendation on this matter. -On a practical level, if you are using auto-renewable subscriptions (and therefore subscription products in the Google play store) you must use the `Purchase.subscribe(sku)` method for initiating the purchase workflow. For non-renewable subscriptions (and therefore regular products in the Google play store), you must use the `Purchase.purchase(sku)` method. +On a practical level, if you are using auto-renewable subscriptions (and so subscription products in the Google play store) you must use the `Purchase.subscribe(sku)` method for initiating the purchase workflow. For non-renewable subscriptions (and so regular products in the Google play store), you must use the `Purchase.purchase(sku)` method. -==== Learning By Example +==== Learning by example -In this section we'll describe the general workflow of subscription management on the server. We also demonstrate how use Apple's and Google's web services to validate receipts and stay informed of important events (such as when users cancel or renew their subscriptions). +In this section you'll describe the general workflow of subscription management on the server. You also show how use Apple's and Google's web services to check receipts and stay informed of important events (such as when users cancel or renew their subscriptions). -==== Building the IAP Demo Project +==== Building the IAP demo project -To aid in this process, we've created a fully-functional in-app purchase demo project that includes both a https://gist.github.com/shannah/b61b9b6b35ea0eac923a54163f5d4deb[client app] and a https://github.com/shannah/cn1-iap-demo-server[server app]. +To aid in this process, Codename One provides a fully functional in-app purchase demo project that includes both a https://gist.github.com/shannah/b61b9b6b35ea0eac923a54163f5d4deb[client app] and a https://github.com/shannah/cn1-iap-demo-server[server app]. -===== Setting up the Client Project +===== Setting up the client project -1. Create a new Codename One project in Netbeans, and choose the "Bare-bones Hello World Template". You should make your package name something unique so that you are able to create real corresponding apps in both Google Play and iTunes connect. -2. Once the project is created, copy https://gist.github.com/shannah/b61b9b6b35ea0eac923a54163f5d4deb[this source file] contents into your main class file. Then change the package name, and class name in the file to match your project settings. E.g. change `package ca.weblite.iapdemo;` to `package ;` and `class IAPDemo implements PurchaseCallback` to `class YourClassName implements PurchaseCallback`. -3. Add the https://github.com/shannah/cn1-generic-webservice-client[Generic Web Service Client] library to your project by going to "Codename Settings" > "Extensions", finding that library, and click "Download". Then "Refresh CN1 libs" as it suggests. -4. Change the `localHost` property to point to your local machine's network address. Using "http://localhost" is not going to cut it here because when the app is running on a phone, it needs to be able to connect to your web server over the network. This address will be your local network address (e.g. 192.168.0.9, or something like that). +1. Create a new Codename One project in Netbeans, and choose the `Bare-bones Hello World Template`. You should make your package name something unique so that you are able to create real corresponding apps in both Google Play and iTunes connect. +2. Once the project is created, copy https://gist.github.com/shannah/b61b9b6b35ea0eac923a54163f5d4deb[this source file] contents into your main class file. Then change the package name, and class name in the file to match your project settings. For example, change `package ca.weblite.iapdemo;` to `package ;` and `class IAPDemo implements PurchaseCallback` to `class YourClassName implements PurchaseCallback`. +3. Add the https://github.com/shannah/cn1-generic-webservice-client[Generic Web Service Client] library to your project by going to "Codename Settings" > `Extensions`, finding that library, and click `Download`. Then "Refresh CN1 libs" as it suggests. +4. Change the `localHost` property to point to your local machine's network address. Using "http://localhost" isn't going to cut it here because when the app is running on a phone, it needs to be able to connect to your web server over the network. This address will be your local network address (for example, 192.168.0.9, or something like that). + [source,java] ---- private static final String localHost = "http://10.0.1.32"; ---- -5. Add the `ios.plistInject` build hint to your project with the value "NSAppTransportSecurity NSAllowsArbitraryLoads ". This is so that we can use http urls in iOS. Since we don't intend to fully publish this app, we can cut corners like this. If you were creating a real app, you would use proper secure URLs. +5. Add the `ios.plistInject` build hint to your project with the value "NSAppTransportSecurity NSAllowsArbitraryLoads ". This is so that you can use http urls in iOS. Since you don't intend to fully publish this app, you can cut corners like this. If you were creating a real app, you would use proper secure URLs. -===== Setting up the Server Project +===== Setting up the server project -Download the CN1-IAP-Server demo project from Github, and run its "install-deps" ANT task in order to download and install its dependencies to your local Maven repo. +Download the CN1-IAP-Server demo project from Github, and run its "install-deps" ANT task to download and install its dependencies to your local Maven repo. -NOTE: For the following commands to work, make sure you have "ant", "mvn", and "git" in your environment PATH. +NOTE: For the following commands to work, make sure you have "ant," "mvn," and "git" in your environment PATH. ---- $ git clone https://github.com/shannah/cn1-iap-demo-server @@ -662,7 +662,7 @@ $ ant install-deps Open the project in Netbeans -===== Setting up the Database +===== Setting up the database 1. Create a new database in your preferred DBMS. Call it anything you like. 2. Create a new table named "RECEIPTS" in this database with the following structure: @@ -683,47 +683,47 @@ create TABLE RECEIPTS primary key (TRANSACTION_ID, STORE_CODE) ) ---- -3. Open the "persistence.xml" file in the server netbeans project. +3. Open the "persistence.xml" file in the server NetBeans project. + image::img/iap3-persistence-file.png[Persistence File,scaledwidth=40%] 4. Change the data source to the database you just created. + image::img/iap3-persistence-file.png[Edit persistence.xml file data source,scaledwidth=40%] -If you're not sure how to create a data source, see my https://www.codenameone.com/blog/connecting-to-a-mysql-database-part-2.html[previous tutorial on connecting to a MySQL database]. +If you're not sure how to create a data source, see your https://www.codenameone.com/blog/connecting-to-a-mysql-database-part-2.html[previous tutorial on connecting to a MySQL database]. -===== Testing the Project +===== Testing the project -At this point we should be able to test out the project in the Codename One simulator to make sure it's working. +At this point you should be able to test out the project in the Codename One simulator to make sure it's working. -1. Build and Run the server project in Netbeans. You may need to tell it which application server you wish to run it on. I am running it on the Glassfish 4.1 that comes bundled with Netbeans. -2. Build and run the client project in Netbeans. This should open the Codename One simulator. +1. Build and Run the server project in Netbeans. You may need to tell it which application server you wish to run it on. I am running it on the Glassfish 4.1 that comes bundled with Netbeans. +2. Build and run the client project in Netbeans. This should open the Codename One simulator. When the app first opens you'll see a screen as follows: .First screen of app image::img/iap3-first-screen.png[First screen of app,scaledwidth=20%] -This screen is for testing consumable products, so we won't be making use of this right now. +This screen is for testing consumable products, so you won't be making use of this right now. -Open the hamburger menu and select "Subscriptions". You should see something like this: +Open the hamburger menu and select `Subscriptions`. You should see something like this: .Subscriptions form image::img/iap3-subscriptions-form.png[Subscriptions form,scaledwidth=20%] -Click on the "Subscribe 1 Month No Ads" button. You will be prompted to accept the purchase: +Click on the "Subscribe 1 Month No Ads" button. You will be prompted to accept the purchase: -.Approve purchase dialog -image::img/iap3-approve-purchase.png[Approve purchase dialog,scaledwidth=40%] +.Approve buy dialog +image::img/iap3-approve-purchase.png[Approve buy dialog,scaledwidth=40%] -Upon completion, the app will submit the purchase to your server, and if all went well, it will retrieve the updated list of receipts from your server also, and update the label on this form to say "No Ads. Expires ": +Upon completion, the app will submit the purchase to your server, and if all went well, it will retrieve the updated list of receipts from your server also, and update the label on this form to say "No Ads. Expires ": -.After successful purchase -image::img/iap3-successful-purchase.png[After successful purchase,scaledwidth=20%] +.After successful buy +image::img/iap3-successful-purchase.png[After successful buy,scaledwidth=20%] -NOTE: This project is set up to use an expedited expiry date schedule for purchases from the simulator. 1 month = 5 minutes. 3 months = 15 minutes. This helps for testing. That is why your expiry date may be different than expected. +NOTE: This project is set up to use an expedited expiry date schedule for purchases from the simulator. 1 month = 5 minutes. 3 months = 15 minutes. This helps for testing. That's why your expiry date may be different than expected. -Just to verify that the receipt was inserted correctly, you should check the contents of your "RECEIPTS" table in your database. In Netbeans, I can do this easily from the "Services" pane. Expand the database connection down to the RECEIPTS table, right click "RECEIPTS" and select "View Data". This will open a data table similar the the following: +Just to verify that the receipt was inserted, you should check the contents of your "RECEIPTS" table in your database. In Netbeans, I can do this from the "Services" pane. Expand the database connection down to the RECEIPTS table, right click "RECEIPTS" and select `View Data`. This will open a data table similar the following: .Receipts table after insertion image::img/iap3-view-table-data.png[Receipts table after insertion,scaledwidth=20%] @@ -733,28 +733,28 @@ image::img/iap3-table-view.png[Table view,scaledwidth=40%] A few things to mention here: -1. The "username" was provided by the client. It's hard-coded to "admin", but the idea is that you would have the user log in and you would have access to their real username. +1. The "username" was provided by the client. It's hard-coded to "admin," but the idea is that you would have the user log in and you would have access to their real username. 2. All dates are stored as unix timestamps in milliseconds. If you delete the receipt from your database, then press the "Synchronize Receipts" button in your app, the app will again say "No subscriptions." Similarly if you wait 5 minutes and hit "Synchronize receipts" the app will say no subscriptions found, and the "ads" will be back. ====== Troubleshooting -Let's not pretend that everything worked for you on the first try. There's a lot that could go wrong here. If you make a purchase and nothing appears to happen, the first thing you should do is check the Network Monitor in the simulator ("Simulate" > "Network" > "Network Monitor"). You should see a list of network requests. Some will be GET requests and there will be at least one POST request. Check the response of these requests to see if they succeeded. +Let's not pretend that everything worked for you on the first try. There's a lot that could go wrong here. If you make a purchase and nothing appears to happen, the first thing you should do is check the Network Monitor in the simulator ("Simulate" > "Network" > "Network Monitor"). You should see a list of network requests. Some will be GET requests and there will be at least one POST request. Check the response of these requests to see if they succeeded. Also check the Glassfish server log to see if there is an exception. -Common problems would be that the URL you have set in the client app for `endpointURL` is incorrect, or that there is a database connection problem. +Common problems would be that the URL you have set in the client app for `endpointURL` is wrong, or that there is a database connection problem. -==== Looking at the Source of the App +==== Looking at the source of the app -Now that we've set up and built the app, let's take a look at the source code so you can see how it all works. +Now that you've set up and built the app, let's take a look at the source code so you can see how it all works. -===== Client Side +===== Client side -I use the https://github.com/shannah/cn1-generic-webservice-client[Generic Webservice Client Library] from inside my `ReceiptStore` implementation to load receipts from the web service, and insert new receipts to the database. +I use the https://github.com/shannah/cn1-generic-webservice-client[Generic Webservice Client Library] from inside your `ReceiptStore` implementation to load receipts from the web service, and insert new receipts to the database. -The source for my ReceiptStore is as follows: +The source for your ReceiptStore is as follows: [source,java] ---- @@ -813,9 +813,9 @@ private ReceiptStore createReceiptStore() { } ---- -Notice that we are not doing any calculation of expiry dates in our client app, as we did in the previous post (on non-renewable receipts). Since we are using a server now, it makes sense to move all of that logic over to the server. +Notice that you aren't doing any calculation of expiry dates in your client app, as you did in the previous post (on non-renewable receipts). Since you are using a server now, it makes sense to move all that logic over to the server. -The `createRESTClient()` method shown there simply creates a `RESTfulWebServiceClient` and configuring it to use basic authentication with a username and password. The idea is that your user would have logged into your app at some point, and you would have a username and password on hand to pass back to the web service with the receipt data so that you can connect the subscription to a user account. The source of that method is listed here: +The `createRESTClient()` method shown there creates a `RESTfulWebServiceClient` and configuring it to use basic authentication with a username and password. The idea is that your user would have logged into your app at some point, and you would have a username and password on hand to pass back to the web service with the receipt data so that you can connect the subscription to a user account. The source of that method is listed here: [source,java] ---- @@ -842,7 +842,7 @@ private RESTfulWebServiceClient createRESTClient(String url) { ===== Server-Side -On the server-side, our REST controller is a standard JAX-RS REST interface. I used Netbeans web service wizard to generate it and then modified it to suit my purposes. The methods of the `ReceiptsFacadeREST` class pertaining to the REST API are shown here: +On the server-side, your REST controller is a standard JAX-RS REST interface. I used Netbeans web service wizard to generate it and then modified it to suit your purposes. The methods of the `ReceiptsFacadeREST` class pertaining to the REST API are shown here: [source,java] ---- @@ -862,7 +862,7 @@ public class ReceiptsFacadeREST extends AbstractFacade { // Save the receipt first in case something goes wrong in the validation stage super.create(entity); - // Let you validate the receipt + // Let's validate the receipt validateAndSaveReceipt(entity); // validates the receipt against appropriate web service // and updates database if expiry date has changed. @@ -882,11 +882,11 @@ public class ReceiptsFacadeREST extends AbstractFacade { } ---- -The magic happens inside that `validateAndSaveReceipt()` method, which I'll cover in detail soon. +The magic happens inside that `validateAndSaveReceipt()` method, which You'll cover in detail soon. ====== Notifications -It's important to note that you will not be notified by apple or google when changes are made to subscriptions. It's up to you to periodically "poll" their web service to find if any changes have been made. Changes we would be interested in are primarily renewals and cancellations. In order to deal with this, set up a method to run periodically (once-per day might be enough). For testing, I actually set it up to run once per minute as shown below: +It's important to note that you won't be notified by apple or google when changes are made to subscriptions. It's up to you to periodically "poll" their web service to find if any changes have been made. Changes you would be interested in are primarily renewals and cancellations. To deal with this, set up a method to run periodically (once-per day might be enough). For testing, I actually set it up to run once per minute as shown below: [source,java] ---- @@ -918,13 +918,13 @@ public void validateSubscriptionsCron() { } ---- -That method simply finds all of the receipts in the database that haven't been validated in some period of time, and validates it. Again, the magic happens inside the `validateAndSaveReceipt()` method which we cover later. +That method finds all the receipts in the database that haven't been validated in some period of time, and validates it. Again, the magic happens inside the `validateAndSaveReceipt()` method which you cover later. -NOTE: In this example we only validate receipts from the iTunes and Play stores because those are the only ones that we currently support auto-renewing subscriptions on. +NOTE: This example only checks receipts from the iTunes and Play stores because those are the only stores that support auto-renewing subscriptions in Codename One. -==== The CN1-IAP-Validator Library +==== The CN1-IAP-Validator library -For the purpose of this tutorial, I created a library to handle receipt validation in a way that hides as much of the complexity as possible. It supports both Google Play receipts and iTunes receipts. +For this tutorial, I created a library to handle receipt validation in a way that hides as much of the complexity as possible. It supports both Google Play receipts and iTunes receipts. The general usage is as follows: @@ -949,9 +949,9 @@ As you can see from this snippet, the complexity of receipt validation has been 2. `GOOGLE_DEVELOPER_API_CLIENT_ID` - A client ID that you'll get from the google developer API console when you set up your API service credentials. 3. `GOOGLE_DEVELOPER_PRIVATE_KEY` - A PKCS8 encoded string with an RSA private key that you'll receive at the same time as the `GOOGLE_DEVELOPER_API_CLIENT_ID`. -I will go through the steps to obtain these values soon. +I will go through the steps to get these values soon. -==== The `validateAndSaveReceipt()` Method +==== The `validateAndSaveReceipt()` method You are now ready to see the full magic of the `validateAndSaveReceipt()` method in all its glory: @@ -1087,100 +1087,100 @@ private Receipts[] validateAndSaveReceipt(Receipts receipt) { } } ---- -<1> We need to handle the case where the app is being used in the CN1 simulator. We'll treat this -as a non-renewable receipt, and we'll calculate the expiry date using an "accelerated" clock to assist in testing. +<1> You need to handle the case where the app is being used in the CN1 simulator. You'll treat this +as a non-renewable receipt, and you'll calculate the expiry date using an "accelerated" clock to assist in testing. -NOTE: In many of the code snippets for the Server-side code, you'll see references to both a `Receipts` class and a `Receipt` class. I know this is slightly confusing. The `Receipts` class is a JPA entity the encapsulates a row from the "receipts" table of our SQL database. The `Receipt` class is `com.codename1.payment.Receipt`. It's used to interface with the IAP validation library. +NOTE: In many of the code snippets for the Server-side code, you'll see references to both a `Receipts` class and a `Receipt` class. I know this is slightly confusing. The `Receipts` class is a JPA entity the encapsulates a row from the "receipts" table of your SQL database. The `Receipt` class is `com.codename1.payment.Receipt`. It's used to interface with the IAP validation library. -==== Google Play Setup +==== Google play setup -===== Creating the App in Google Play +===== Creating the app in Google play -In order to test out in-app purchase on an Android device, you'll need to create an app the https://play.google.com/apps/publish/[Google Play Developer Console]. I won't describe the process in this section, but there is plenty of information around the internet on how to do this. Some useful references for this include: +To test out in-app purchase on an Android device, you'll need to create an app the https://play.google.com/apps/publish/[Google Play Developer Console]. I won't describe the process in this section, but there is plenty of information around the internet on how to do this. Some useful references for this include: . https://developer.android.com/distribute/googleplay/start.html[Getting Started With Publishing] - If you don't already have an account with Google to publish your apps. . https://developer.android.com/distribute/tools/launch-checklist.html[Launch Checklist] -====== Graphics, Icons, etc.. +====== Graphics, icons, etc -You are required to upload some screenshots and feature graphics. Don't waste time making these perfect. For the screenshots, you can just use the "Screenshot" option in the simulator. (Use the Nexus 5 skin). For the feature graphics, I used https://www.norio.be/android-feature-graphic-generator/[this site] that will generate the graphics in the correct dimensions for Google Play. You can also just leave the icon as the default Codename One icon. +You are required to upload some screenshots and feature graphics. Don't waste time making these perfect. For the screenshots, you can just use the "Screenshot" option in the simulator. (Use the Nexus 5 skin). For the feature graphics, I used https://www.norio.be/android-feature-graphic-generator/[this site] that will generate the graphics in the correct dimensions for Google Play. You can also just leave the icon as the default Codename One icon. -====== Creating Test Accounts +====== Creating test accounts -IMPORTANT: You cannot purchase in-app products from your app using your publisher account. You need to set up at least one test account for the purpose of testing the app. +IMPORTANT: You can't buy in-app products from your app using your publisher account. You need to set up at least one test account for testing the app. -In order to test your app, you need to set up a test account. A test account must be associated with a real gmail email address. If you have a domain that is managed by Google apps, then you can also use an address from that domain. +To test your app, you need to set up a test account. A test account must be associated with a real gmail email address. If you have a domain that's managed by Google apps, then you can also use an address from that domain. -The full process for testing in-app billing can be found in https://developer.android.com/google/play/billing/billing_testing.html[this google document]. However, I personally found this documentation difficult to follow. +The full process for testing in-app billing can be found in https://developer.android.com/google/play/billing/billing_testing.html[this google document]. But, I personally found this documentation difficult to follow. -For your purposes, you'll need to set up a tester list in Google Play. Choose "Settings" > "Tester Lists". Then create a list with all of the email address that you want to have treated as test accounts. Any purchases made by these email addresses will be treated as "Sandbox" purchases, and won't require real money to change hands. +For your purposes, you'll need to set up a tester list in Google Play. Choose "Settings" > `Tester Lists`. Then create a list with all the email address that you want to have treated as test accounts. Any purchases made by these email addresses will be treated as "Sandbox" purchases, and won't require real money to change hands. -====== Alpha Channel Distribution +====== Alpha channel distribution -In order to test in-app purchase on Android, you *must* first publish your app. You can't just build and install your app manually. The app needs to be published on the Play store, and it must be installed *through* the play store for in-app purchase to work. Luckily you can publish to an Alpha channel so that your app won't be publicly available. +To test in-app purchase on Android, you *must* first publish your app. You can't just build and install your app manually. The app needs to be published on the Play store, and it must be installed *through* the play store for in-app purchase to work. You can publish to an Alpha channel so that your app won't be publicly available. For more information about setting up alpha testing on Google play see https://support.google.com/googleplay/android-developer/answer/3131213?hl=en[this Google support document on the subject]. -Once you have set your app up for alpha testing, you can send an invite link to your test accounts. You can find the link in the Google Play console under the APK section, under the "Alpha" tab (and assuming you've enabled alpha testing. +Once you have set your app up for alpha testing, you can send an invite link to your test accounts. You can find the link in the Google Play console under the APK section, under the "Alpha" tab (and assuming you've enabled alpha testing. .Alpha testing tab in google play image::img/iap3-alpha-testing-tab.png[Alpha testing tab in google play,scaledwidth=40%] -The format of the link is `https://play.google.com/apps/testing/your-app-id` in case you can't find it. You can email this to your alpha testers. Make sure that you have added all testers to your tester lists so that their purchases will be made in the sandbox environment. +The format of the link is `https://play.google.com/apps/testing/your-app-id` in case you can't find it. You can email this to your alpha testers. Make sure that you have added all testers to your tester lists so that their purchases will be made in the sandbox environment. Also, before proceeding with testing in-app purchases, you need to add the in-app products in Google Play. -====== Adding In-App Products +====== Adding In-App products -After you have published your APK to the alpha channel, you can create the products. For the purposes of this tutorial, we'll just add two products: +After you have published your APK to the alpha channel, you can create the products. For the purposes of this tutorial, you'll just add two products: . **iapdemo.noads.month.auto** - The 1 month subscription. . **iapdemo.noads.3month.auto** - The 3 month subscription. -IMPORTANT: Since we will be adding products as "Subscriptions" in the pay store, your app *must* use the `Purchase.subscribe(sku)` method for initiating a purchase on these products, and *not* the `Purchase.purchase(sku)` method. If you accidentally use `purchase()` to purchase a subscription on Android, the payment will go through, but your purchase callback will receive an error. +IMPORTANT: Since you will be adding products as "Subscriptions" in the pay store, your app *must* use the `Purchase.subscribe(sku)` method for initiating a purchase on these products, and *not* the `Purchase.purchase(sku)` method. If you use `purchase()` to buy a subscription on Android, the payment will go through, but your buy callback will receive an error. **Adding 1 month Subscription** 1. Open Google Play Developer Console, and navigate to your app. -2. Click on "In-app Products" in the menu. Then click the "Add New Product" button. -3. Select "Subscription", and enter "iapdemo.noads.month.auto" for the Product ID. Then click "Continue" +2. Click on "In-app Products" in the menu. Then click the "Add New Product" button. +3. Select `Subscription`, and enter "iapdemo.noads.month.auto" for the Product ID. Then click "Continue" .Add new product dialog image::img/iap3-play-add-new-product.png[Add new product dialog,scaledwidth=30%] -Now fill in the form. You can choose your own price and name for the product. The following is a screenshot of the options I chose. +Now fill in the form. You can choose your own price and name for the product. The following is a screenshot of the options I chose. .Add product to google image::img/iap3-add-product-google.png[Add product to google] **Adding 3 month Subscription** -Follow the same process as for the 1 month subscription except use "iapdemo.noads.3month.auto" for the product ID, and select "3 months" for the billing period instead of "Monthly". +Follow the same process as for the 1 month subscription except use "iapdemo.noads.3month.auto" for the product ID, and select "3 months" for the billing period instead of `Monthly`. -===== Testing The App +===== Testing the app -At this point we should be ready to test our app. Assuming you've installed the app using the invite link you sent yourself from Google play, **as a test account that is listed on your testers list**, you should be good to go. +At this point you should be ready to test your app. Assuming you've installed the app using the invite link you sent yourself from Google play, **as a test account that's listed on your testers list**, you should be good to go. -Open the app, click on "Subscriptions", and try to purchase a 1-month subscription. If all goes well, it should insert the subscription into your database. But with no expiry date, since we haven't yet implemented receipt validation yet. We'll do that next. +Open the app, click on `Subscriptions`, and try to buy a 1-month subscription. If all goes well, it should insert the subscription into your database. But with no expiry date, since you haven't yet implemented receipt validation yet. You'll do that next. -===== Creating Google Play Receipt Validation Credentials +===== Creating Google play receipt validation credentials -Google play receipt validation is accomplished via the https://developers.google.com/android-publisher/api-ref/purchases/subscriptions/get[android-publisher Purchases: get API]. The CN1-IAP-Validation library shields you from most of the complexities of using this API, but you still need to obtain a "private key" and a "client id" to access this API. Both of these are provided when you set up an https://developers.google.com/identity/protocols/OAuth2ServiceAccount[OAuth2 Service Account] for your app. +Google play receipt validation is accomplished via the https://developers.google.com/android-publisher/api-ref/purchases/subscriptions/get[android-publisher Purchases: get API]. The CN1-IAP-Validation library shields you from most of the complexities of using this API, but you still need to get a "private key" and a "client id" to access this API. Both of these are provided when you set up an https://developers.google.com/identity/protocols/OAuth2ServiceAccount[OAuth2 Service Account] for your app. -NOTE: The following steps assume that you have already created your app in Google play and have published it to at least the alpha channel. See my previous post on this topic here (Link to be provided). +NOTE: The following steps assume that you have already created your app in Google play and have published it to at least the alpha channel. See your previous post on this topic here (Link to be provided). Steps: -1. Open the https://console.developers.google.com/apis[Google API Developer Console], and select your App from the the menu. +1. Open the https://console.developers.google.com/apis[Google API Developer Console], and select your App from the menu. 2. Click on the "Library" menu item in the left menu, and then click the "Google Play Developer API" link. + + .Google Play Developer API Link image::img/iap3-play-developer-api-link.png[Google Play Developer API Link,scaledwidth=20%] -3. Click on the button that says "Enable". (If you already have it enabled, then just proceed to the next step). +3. Click on the button that says `Enable`. (If you already have it enabled, then just proceed to the next step). + .Enable API button image::img/iap3-enable-api.png[Enable API button,scaledwidth=20%] @@ -1189,15 +1189,15 @@ image::img/iap3-enable-api.png[Enable API button,scaledwidth=20%] + .Credentials dropdown image::img/iap3-credentials-dropdown.png[Credentials dropdown,scaledwidth=20%] -6. You will be presented with a new form. In the "Service Account" drop-down, select "New Service Account". This will give you some additional options. +6. You will be presented with a new form. In the "Service Account" drop-down, select `New Service Account`. This will give you some more options. + .Create service account key image::img/iap3-create-service-account-key.png[Create service account key,scaledwidth=20%] -7. Enter anything you like for the "Service account name". For the role, we'll select "Project" > "Owner" for now just so we don't run into permissions issues. You'll probably want to investigate further to fine a more limited role that only allows receipt verification, but for now, I don't want any unnecessary road blocks for getting this to work. We're probably going to run into "permission denied" errors at first anyways, so the fewer reasons for this, the better. +7. Enter anything you like for the `Service account name`. For the role, you'll select "Project" > "Owner" for now just so you don't run into permissions issues. You'll probably want to investigate further to fine a more limited role that only allows receipt verification, but for now, I don't want any unnecessary road blocks for getting this to work. You're probably going to run into "permission denied" errors at first anyways, so the fewer reasons for this, the better. 8. It will auto-generate an account ID for you. -9. Finally, for the "Key type", select "JSON". Then click the "Create" button. +9., for the `Key type`, select `JSON`. Then click the "Create" button. -This should prompt the download of a JSON file that will have contents similar to the following: +This should prompt the download of a JSON file that will have contents like the following: [source,json] ---- @@ -1215,11 +1215,11 @@ This should prompt the download of a JSON file that will have contents similar t } ---- -This is where we get the information we're looking for. The "client_email" is what we'll use for your `googleClientId`, and the "private_key" is what we'll use for the `googlePrivateKey`. +This is where you get the information you're looking for. The "client_email" is what you'll use for your `googleClientId`, and the "private_key" is what you'll use for the `googlePrivateKey`. -WARNING: Use the "client_email" value as our client ID, not the "client_id" value as you might be tempted to do. +WARNING: Use the "client_email" value as your client ID, not the "client_id" value as you might be tempted to do. -We'll set these in our constants: +You'll set these in your constants: [source,java] ---- @@ -1234,30 +1234,30 @@ validator.setGooglePrivateKey(GOOGLE_DEVELOPER_PRIVATE_KEY); **NOT DONE YET** -Before we can use these credentials to verify receipts for our app, we need to link our app to this new service account from within Google Play. +Before you can use these credentials to verify receipts for your app, you need to link your app to this new service account from within Google Play. Steps: -1. Open the https://play.google.com/apps/publish/[Google Play Developer Console], then click on "Settings" > "API Access". -2. You should see your app listed on this page. Click the "Link" button next to your app. +1. Open the https://play.google.com/apps/publish/[Google Play Developer Console], then click on "Settings" > `API Access`. +2. You should see your app listed on this page. Click the "Link" button next to your app. + .Link to API image::img/iap3-link-to-api.png[Link to API,scaledwidth=20%] -3. This should reveal some more options on the page. You should see a "Service Accounts" section with a list of all of the service accounts that you have created. Find the one we just created, and click the "Grant Access" button in its row. +3. This should reveal some more options on the page. You should see a "Service Accounts" section with a list of all the service accounts that you have created. Find the one you just created, and click the "Grant Access" button in its row. + .Grant access image::img/iap3-grant-access.png[Grant access,scaledwidth=20%] -4. This will open a dialog titled "Add New User". Leave everything default, except change the "Role" to "Administrator". This provides "ALL" permissions to this account, which probably isn't a good idea for production. Later on, after everything is working, you can circle back and try to refine permissions. For the purpose of this tutorial, I just want to pull out all of the potential road blocks. +4. This will open a dialog titled `Add New User`. Leave everything default, except change the "Role" to `Administrator`. This provides "ALL" permissions to this account, which probably isn't a good idea for production. Later on, after everything is working, you can circle back and try to refine permissions. For this tutorial, I just want to pull out all the potential road blocks. + .New User image::img/iap3-new-user.png[New User,scaledwidth=30%] 5. Press the "Add User" button. -At this point, the service account *should* be active so we can try to validate receipts. +At this point, the service account *should* be active so you can try to check receipts. -====== Testing Receipt Validation +====== Testing receipt validation -The `ReceiptsFacadeREST` class includes a flag to enable/disable play store validation. By default it's disabled. Let's enable it: +The `ReceiptsFacadeREST` class includes a flag to enable/disable play store validation. By default it's disabled. Let's enable it: [source,java] ---- @@ -1266,43 +1266,43 @@ public static final boolean DISABLE_PLAY_STORE_VALIDATION=true; Change this to `false`. -Then build and run the server app. The `validateSubscriptionsCron()` method is set to run once per minute, so we just need to wait for the timer to come up and it should try to validate all of the play store receipts. +Then build and run the server app. The `validateSubscriptionsCron()` method is set to run once per minute, so you just need to wait for the timer to come up and it should try to check all the play store receipts. -NOTE: I'm assuming you've already added a receipt in the previous test that we did. If necessary, you should purchase the subscription again in your app. +NOTE: You're assuming you've already added a receipt in the previous test that you did. If necessary, you should buy the subscription again in your app. -After a minute or so, you should see "----------- VALIDATING RECEIPTS ---------" written in the Glassfish log, and it will validate your receipts. If it works, your receipt's expiry date will get populated in the database, and you can press "Synchronize Receipts" in your app to see this reflected. If it fails, there will like be a big ugly stack trace and exception readout with some clues about what went wrong. +After a minute or so, you should see "----------- VALIDATING RECEIPTS ---------" written in the Glassfish log, and it will check your receipts. If it works, your receipt's expiry date will get populated in the database, and you can press "Synchronize Receipts" in your app to see this reflected. If it fails, there will like be a big ugly stack trace and exception readout with some clues about what went wrong. -Realistically, your first attempt will fail for some reason. Use the error codes and stack traces to help lead you to the problem. And feel free to post questions here. +Realistically, your first try will fail for some reason. Use the error codes and stack traces to help lead you to the problem. And feel free to post questions here. -==== iTunes Connect Setup +==== iTunes connect setup -The process for setting up and testing your app on iOS is much simpler than on Android (IMHO). It took me a couple hours to get the iTunes version working, vs a couple days on the Google Play side of things. One notable difference that makes things simpler is that you don't need to actually upload your app to the store to test in-app purchase. You can just use your debug build on your device. It's also *much* easier to roll a bunch of test accounts than on Google Play. You don't need to set up an alpha program, you just create a few "test accounts" (and this is easy to do) in your iTunes connect account, and then make sure to use one of these accounts when making a purchase. You can easily switch accounts on your device from the "Settings" app, where you can just log out of the iTunes store - which will cause you to be prompted in your app the next time you make a purchase. +The process for setting up and testing your app on iOS is much simpler than on Android (IMHO). It took you a couple hours to get the iTunes version working, vs a couple days on the Google Play side of things. One notable difference that makes things simpler is that you don't need to actually upload your app to the store to test in-app purchase. You can just use your debug build on your device. It's also *much* easier to roll a bunch of test accounts than on Google Play. You don't need to set up an alpha program, you just create a few "test accounts" (and this is easy to do) in your iTunes connect account, and then make sure to use one of these accounts when making a purchase. You can switch accounts on your device from the "Settings" app, where you can just log out of the iTunes store - which will cause you to be prompted in your app the next time you make a purchase. -===== Setting up In-App Products +===== Setting up In-App products -The process to add products in iTunes connect is outlined https://developer.apple.com/library/content/documentation/LanguagesUtilities/Conceptual/iTunesConnectInAppPurchase_Guide/Chapters/CreatingInAppPurchaseProducts.html#//apple_ref/doc/uid/TP40013727-CH3-SW1[in this apple developer document]. We'll add our two SKUs: +The process to add products in iTunes connect is outlined https://developer.apple.com/library/content/documentation/LanguagesUtilities/Conceptual/iTunesConnectInAppPurchase_Guide/Chapters/CreatingInAppPurchaseProducts.html#//apple_ref/doc/uid/TP40013727-CH3-SW1[in this apple developer document]. You'll add your two SKUs: . **iapdemo.noads.month.auto** - The 1 month subscription. . **iapdemo.noads.3month.auto** - The 3 month subscription. -Just make sure you add them as auto-renewable subscriptions, and that you specify the appropriate renewal periods. Use the SKU as the product ID. Both of these products will be added to the same subscription group. Call the group whatever you like. +Just make sure you add them as auto-renewable subscriptions, and that you specify the appropriate renewal periods. Use the SKU as the product ID. Both of these products will be added to the same subscription group. Call the group whatever you like. -===== Creating Test Accounts +===== Creating test accounts -In order to test purchases, you need to create some test accounts. See https://developer.apple.com/library/content/documentation/LanguagesUtilities/Conceptual/iTunesConnect_Guide/Chapters/SettingUpUserAccounts.html#//apple_ref/doc/uid/TP40011225-CH25-SW10[this apple document] for details on how to create these test accounts. Don't worry, the process is much simpler than for Android. It should take you under 5 minutes. +To test purchases, you need to create some test accounts. See https://developer.apple.com/library/content/documentation/LanguagesUtilities/Conceptual/iTunesConnect_Guide/Chapters/SettingUpUserAccounts.html#//apple_ref/doc/uid/TP40011225-CH25-SW10[this apple document] for details on how to create these test accounts. Don't worry, the process is much simpler than for Android. It should take you under 5 minutes. Once you have the test accounts created, you should be set to test the app. 1. Make sure your server is running. -2. Log out from the app store. The process is described https://support.apple.com/en-ca/HT203983[here]. +2. Log out from the app store. The process is described https://support.apple.com/en-ca/HT203983[here]. 3. Open your app. -4. Try to purchase a 1-month subscription +4. Try to buy a 1-month subscription -If all went well, you should see the receipt listed in the RECEIPTS table of your database. But the expiry date will be null. We need to set up receipt verification in order for this to work. +If all went well, you should see the receipt listed in the RECEIPTS table of your database. But the expiry date will be null. You need to set up receipt verification in order for this to work. -===== Setting up Receipt Verification +===== Setting up receipt verification -In order for receipt verification to work we simply need to generate a shared secret in iTunes connect. The process is described https://developer.apple.com/library/content/documentation/LanguagesUtilities/Conceptual/iTunesConnectInAppPurchase_Guide/Chapters/CreatingInAppPurchaseProducts.html[here]. +In order for receipt verification to work you need to generate a shared secret in iTunes connect. The process is described https://developer.apple.com/library/content/documentation/LanguagesUtilities/Conceptual/iTunesConnectInAppPurchase_Guide/Chapters/CreatingInAppPurchaseProducts.html[here]. Once you have a shared secret, update the ReceiptsFacadeREST class with the value: @@ -1320,6 +1320,6 @@ public static final boolean DISABLE_ITUNES_STORE_VALIDATION=true; Change this to `false`. -If you rebuild and run the server project, and wait for the `validateSubscriptionsCron()` method to run, it should validate the receipt. After about a minute (or less), you'll see the text "----------- VALIDATING RECEIPTS ---------" written to the Glassfish log file, followed by some output from connecting to the iTunes validation service. If all went well, you should see your receipt expiration date updated in the database. If not, you'll likely see some exception stack traces in the Glassfish log. +If you rebuild and run the server project, and wait for the `validateSubscriptionsCron()` method to run, it should check the receipt. After about a minute (or less), you'll see the text "----------- VALIDATING RECEIPTS ---------" written to the Glassfish log file, followed by some output from connecting to the iTunes validation service. If all went well, you should see your receipt expiration date updated in the database. If not, you'll likely see some exception stack traces in the Glassfish log. -NOTE: Sandbox receipts in the iTunes store are set to run on an accelerated schedule. A 1 month subscription is actually 5 minutes, 3 months is 15 minutes etc... Also sandbox subscriptions don't seem to persist in perpetuity until the user has cancelled it. I have found that they usually renew only 4 or 5 times before they are allowed to lapse by Apple. +NOTE: Sandbox receipts in the iTunes store are set to run on an accelerated schedule. A 1 month subscription is actually 5 minutes, 3 months is 15 minutes etc... Also sandbox subscriptions don't seem to persist in perpetuity until the user has cancelled it. I have found that they renew only 4 or 5 times before they are allowed to lapse by Apple. diff --git a/docs/developer-guide/Native-Themes.asciidoc b/docs/developer-guide/Native-Themes.asciidoc index 796b8b4a7c..67953807d4 100644 --- a/docs/developer-guide/Native-Themes.asciidoc +++ b/docs/developer-guide/Native-Themes.asciidoc @@ -1,4 +1,4 @@ -== Native Modern Themes +== Native modern themes Codename One ships an iOS Modern (liquid-glass) theme and an Android Material 3 theme that you can opt your app into with a single build @@ -271,7 +271,7 @@ iOS-only behaviour: * `Toolbar`, `TitleArea`, `Title` paint over the status-bar area. iOS reserves room above for the notch / status bar; in your - capture this reads as a coloured strip at the very top of the + capture this reads as a coloured strip at the top of the Form. In production the device fills it with system content (signal, battery, time). * `MultiButton` is styled as an iOS Settings row (multi-line text @@ -280,7 +280,7 @@ iOS-only behaviour: Android-only behaviour: -* `Toolbar` does not paint over the system status bar; the native +* `Toolbar` doesn't paint over the system status bar; the native Android status bar handles that. * `Tabs` use Material 3 top tabs (flat, underline-by-color). iOS modern renders Tabs as a bottom-anchored pill group via the @@ -300,7 +300,7 @@ constants (Material codepoints): |`@checkBoxCheckedIconInt` |`MATERIAL_CHECK_BOX` (`E834`) -|Override to e.g. `MATERIAL_CHECK_CIRCLE` (`E86C` / `59500`) for an +|Override to for example, `MATERIAL_CHECK_CIRCLE` (`E86C` / `59500`) for an iOS-style filled circle. The iOS modern theme does this. |`@checkBoxUncheckedIconInt` @@ -400,7 +400,7 @@ DangerButton.pressed { ---- `cn1-derive` works best when the relationship is *child refines -parent*. Avoid deriving across unrelated UIIDs (e.g. a TitleArea +parent*. Avoid deriving across unrelated UIIDs (for example, a TitleArea that derives from Toolbar) - inline the properties instead. === Translucency, glass effects, and the test harness diff --git a/docs/developer-guide/Push-Notifications.asciidoc b/docs/developer-guide/Push-Notifications.asciidoc index dc4f614865..bc3eecd348 100644 --- a/docs/developer-guide/Push-Notifications.asciidoc +++ b/docs/developer-guide/Push-Notifications.asciidoc @@ -1,4 +1,4 @@ -== Push Notifications +== Push notifications [[push-notifications-section,Codename One Push Notifications]] This chapter discusses push support in Codename One applications. It covers how to set up push on the various platforms, how to respond to push notifications in your app, and how to send push notifications to your app. @@ -7,16 +7,16 @@ NOTE: Push notifications require a Codename One Pro account or higher. You must TIP: For a quick reference on setting up Push notifications, check out the https://www.codenameone.com/files/push-cheatsheet.pdf[Push Cheatsheet]. -=== Understanding Push Notifications +=== Understanding push notifications -Push notifications provide a way to inform users that something of interest has taken place. it's one of the few mechanisms available to interact with the user *even when the app isn't running*. If your app is registered to receive push notifications, then you can send messages to the user's device through REST API either from another device, or, as is usually the case, from a web server. +Push notifications provide a way to inform users that something of interest has taken place. it's one of the few mechanisms available to interact with the user *even when the app isn't running*. If your app is registered to receive push notifications, then you can send messages to the user's device through REST API either from another device, or, as is the case, from a web server. Messages may contain a short title and text body that will be displayed to the user in their device's messages stream. They may also specify a badge to display on the app's icon (for example: that red circle on your mail app icon that indicates how many unread messages you've), a sound to play then the message arrives, an image attachment, and a set of "actions" that the user can perform directly in the push notification. -In addition to messages that the user *sees*, a push notification can contain non-visual information that's *silently* sent to your app. +Also to messages that the user *sees*, a push notification can contain non-visual information that's *silently* sent to your app. -=== Implementing Push Support +=== Implementing push support Enabling push support in your application is a matter of implementing the `PushCallback` interface in your application's main class (that's: the class that includes your `start()`, `init()`, etc... methods. @@ -57,40 +57,40 @@ public class MyApplication implements PushCallback { ---- -There will be additional steps required to deploy to each platform (for example: iOS requires you to generate push certificates, Android needs you to register your app ID with their cloud messaging platform, etc...), but, fundamentally, this is all that's required to enable push support in your app. +There will be more steps required to deploy to each platform (for example: iOS requires you to generate push certificates, Android needs you to register your app ID with their cloud messaging platform, etc...), but, fundamentally, this is all that's required to enable push support in your app. -=== The Push Lifecycle +=== The push lifecycle -Let you take minute to go over the three callbacks in your application. +Let's take minute to go over the three callbacks in your application. ==== Registration -When your application first opens, it needs to register with the platform's central cloud messaging infrastructure. On Android this involves a call to their GCM/FCM server; on iOS, the call will be to the APNS server. And so on. That server will return a unique device ID that can be used to send push notifications to the device. This device ID will then be passed to your `registeredForPush()` method as the `deviceId` parameter so that you can save it somewhere. Typically you would send this value to your own web service so that you can use it to send notifications to the device later on. The device ID will generally not change unless you uninstall and reinstall the app, but you will receive the callback every time the app starts. +When your application first opens, it needs to register with the platform's central cloud messaging infrastructure. On Android this involves a call to their GCM/FCM server; on iOS, the call will be to the APNS server. And more. That server will return a unique device ID that can be used to send push notifications to the device. This device ID will then be passed to your `registeredForPush()` method as the `deviceId` parameter so that you can save it somewhere. Typically you would send this value to your own web service so that you can use it to send notifications to the device later on. The device ID won't change unless you uninstall and reinstall the app, but you will receive the callback every time the app starts. -IMPORTANT: The `deviceId` parameter can't be used directly when sending push messages through the Codename One push service. It needs to be prefixed with a platform identifier so the that push server knows which messaging service to route the message through. You can obtain the complete device ID, including the platform prefix, by calling `Push.getPushKey()` +IMPORTANT: The `deviceId` parameter can't be used directly when sending push messages through the Codename One push service. It needs to be prefixed with a platform identifier so the that push server knows which messaging service to route the message through. You can get the complete device ID, including the platform prefix, by calling `Push.getPushKey()` -If the registration failed for some reason, the the `pushRegistrationError()` callback will be fired instead. +If the registration failed for some reason, the `pushRegistrationError()` callback will be fired instead. -Notice that all of this happens seamlessly behind the scenes when your app loads. You don't need to initiate any of this workflow. +Notice that all this happens seamlessly behind the scenes when your app loads. You don't need to start any of this workflow. -==== Sending a Push Notification +==== Sending a push notification Codename One provides a secure REST API for sending push notifications. As an HTTP API, it's language agnostic. You can send push notifications to your app using Java, PHP, Python, Ruby, or even by hand using something like curl. Each HTTP request can contain a push message and a list of device IDs to which the message should be sent. You don't need to worry about whether your app is running on Android, iOS, Windows, or the web. A single HTTP request can send a message to many devices at once. -==== Receiving a Push Notification +==== Receiving a push notification -There are two different scenarios to be aware of when it comes to receiving push notifications. If your app is running in the foreground when the message arrives, then it will be passed directly to your `push()` callback. If your app is either in the background, or not running, then the notification will be displayed in your device's notifications. If the user then taps the notification, it will open your app, and the `push()` callback will be run with the contents of the message. +two different scenarios to be aware of when it comes to receiving push notifications. If your app is running in the foreground when the message arrives, then it will be passed directly to your `push()` callback. If your app is either in the background, or not running, then the notification will be displayed in your device's notifications. If the user then taps the notification, it will open your app, and the `push()` callback will be run with the contents of the message. Some push message types include hidden content that won't be displayed in your device's notifications. These hidden messages (or portions of messages) are passed directly to the `push()` callback of your app for processing. NOTE: On iOS, hidden push messages (push type 2) won't be delivered when the app is in the background. -TIP: You can set the `android.background_push_handling` build hint to "true" to deliver push messages on Android when the app is minimized (running in the background). There is no equivalent setting on other platforms currently. +TIP: You can set the `android.background_push_handling` build hint to "true" to deliver push messages on Android when the app is minimized (running in the background). no equivalent setting on other platforms. [[delay-push-completion]] -=== Handling Long-Running Background Push Tasks +=== Handling Long-Running background push tasks -By default, the platform signals the completion of the push processing immediately after your `push(String)` callback returns. For example, some tasks, such as playing audio (for example: for Push-To-Talk apps), require more time to complete. If the app is in the background, the OS might suspend the app before the task completes if it thinks the push handling is finished. +By default, the platform signals the completion of the push processing after your `push(String)` callback returns. For example, some tasks, such as playing audio (for example: for Push-To-Talk apps), require more time to complete. If the app is in the background, the OS might suspend the app before the task completes if it thinks the push handling is finished. To handle this, you can opt-in to manually signaling the completion of the push task using the `delayPushCompletion` build hint. @@ -123,10 +123,10 @@ public void push(String message) { * **Android:** The system acquires a `PARTIAL_WAKE_LOCK` when the push is received, keeping the CPU running even if the screen is off. Calling `notifyPushCompletion()` releases this lock. The lock has a safety timeout (for example:, 30 seconds) to prevent battery drain if you forget to call it. * **iOS:** The system delays calling the completion handler passed to the push delegate. This provides your app background execution time. Calling `notifyPushCompletion()` invokes the system completion handler. -NOTE: If you enable this feature, you **must** call `Display.getInstance().notifyPushCompletion()` in all code paths of your `push()` callback to ensure the device can sleep/suspend properly. +NOTE: If you enable this feature, you **must** call `Display.getInstance().notifyPushCompletion()` in all code paths of your `push()` callback to ensure the device can sleep/suspend. -=== Testing Push Support +=== Testing push support The easiest way to test push notifications in your app is to use the push simulation feature of the Codename One Simulator. @@ -137,12 +137,12 @@ The "Registered Successfully" button will trigger your app's `registeredForPush( The "Send" button will trigger the `push()` callback with the message body that you place in the "Send Message" field. -The "Push Type" drop-down allows you to select the "type" of the push message. This dictates how the message body (that's: the contents of the "Send Message" field) is interpreted. Some push types simply pass the message to the device verbatim, while others assume that the message contains structure that's meant to be parsed by the client to extract such things as badges, sounds, images, and actions that are associated with the message. You will go over the available push types in a moment, but for now, you will keep it simple by using a push type of "1" - which sends the message verbatim. +The "Push Type" drop-down allows you to select the "type" of the push message. This dictates how the message body (that's: the contents of the "Send Message" field) is interpreted. Some push types pass the message to the device verbatim, while others assume that the message contains structure that's meant to be parsed by the client to extract such things as badges, sounds, images, and actions that are associated with the message. You will go over the available push types in a moment, but for now, you will keep it simple by using a push kind of "1" - which sends the message verbatim. .Sending a basic hello world push from the push simulator image::img/push-push-simulator-test-1.png[Sending a basic Hello World push,scaledwidth=30%] -Let you try a simple "hello world" push message. Select "1" from the "Push Type" drop-down menu, and enter "Hello World" into the "Send Message" field as shown above. Then press "send". +Let's try a simple "hello world" push message. Select "1" from the "Push Type" drop-down menu, and enter "Hello World" into the "Send Message" field as shown above. Then press "send." Assuming your `push()` method looks like: @@ -164,7 +164,7 @@ This experiment simulated a push notification while the app is running in the fo .Pausing the app in the simulator so you can simulate push notifications while app is in the background. image::img/push-pause-app.png[Pausing app in the simulator,scaledwidth=30%] -When the app is paused it will simply display a white screen in the simulator with the text "Paused" in the middle. +When the app is paused it will display a white screen in the simulator with the text "Paused" in the middle. Now return to the push simulator again, and press "Send" again with same values in the other fields (Push type 1, and Message "Hello World"). Rather than running the `push()` callback this time, it will display a popup dialog outside the app, as shown below. @@ -173,10 +173,10 @@ image::img/push-hello-world-simulator-paused.png[Push popup when app paused,scal While this popup dialog doesn't replicate what a push notification will look like in a device's notifications stream when the app is closed, it does simulate the conceptual workflow. The process whereby the user is notified of the message outside of the app, and the app isn't notified until/unless the user taps on the notification. -If you monitor the console for your app, you should notice that the `push()` callback hasn't been called yet for this notification, but if you click "OK" in the dialog, your `push()` callback will be run. Clicking OK is analogous to the user tapping on the notification. If you simply close the dialog box (by clicking the "x" in the corner), this would be analogous to the user dismissing the notification. In this case the `push()` callback would not be called at all. +If you monitor the console for your app, you should notice that the `push()` callback hasn't been called yet for this notification, but if you click "OK" in the dialog, your `push()` callback will be run. Clicking OK is analogous to the user tapping on the notification. If you close the dialog box (by clicking the "x" in the corner), this would be analogous to the user dismissing the notification. In this case the `push()` callback would not be called at all. [[push-message-types-section]] -=== Push Types and Message Structure +=== Push types and message structure The example above sends a simple message to be displayed to the user. Push notifications can include more data than an alert message, though. When the selected "push type" is 0 or 1, the push server will interpret the message body a simple alert string. Selecting a different push type will affect how the message body is interpreted. The following push types are supported: @@ -192,17 +192,17 @@ When active this will trigger the push(String) method twice, once with the visua - `5` - Sends a regular push message but doesn't play a sound when the push arrives -- `99` - The message body is expected to be XML, where the root element contains at least `type` and `body` attributes which correspond to one of the other push push types and message body respectively. This push type supports additional information such as image attachments and push actions. For example: `` +- `99` - The message body is expected to be XML, where the root element contains at least `type` and `body` attributes which correspond to one of the other push types and message body respectively. This push type supports more information such as image attachments and push actions. For example: `` - `100` - Applicable to iOS and Windows. Allows setting the numeric badge on the icon to the given number. The body of the message must be a number for example: unread count. -- `101` - identical to 100 with an added message payload separated with a space. For example: `30 you've 30 unread messages` will set the badge to "30" and present the push notification text of "you've 30 unread messages". Supported on Android, iOS, and Windows. +- `101` - identical to 100 with an added message payload separated with a space. For example: `30 you've 30 unread messages` will set the badge to "30" and present the push notification text of "you've 30 unread messages." Supported on Android, iOS, and Windows. - `102` - Combines badge, title, and body in a single payload formatted as `badge;title;body`. Supported on Android, iOS, and Windows. The following sections will show examples of the various kinds of pushes. You can try them out yourself by opening the push simulator. -==== Example Push Type 1 +==== Example push type 1 **Push Type 1; Message Body: "Hello World"** @@ -220,17 +220,17 @@ image::img/push-type-1-example-hello-chrome-desktop.png[Push type 1 on Chrome de In all cases, if the user taps/clicks the notification, it will bring the app to the foreground and call the `push()` callback with "Hello World" as the argument. -==== Example Push Type 2 +==== Example push type 2 **Push Type 2; Message Body: "Hello World"** -Push type 2 is a hidden push so it will behave differently on different platforms. On Android (before API 27), the `push()` callback will be fired even if the app is in the background. After API 27, it will be fired the moment the app is put in the foreground. On iOS, it will simply be ignored if the app is in the background. +Push type 2 is a hidden push so it will behave differently on different platforms. On Android (before API 27), the `push()` callback will be fired even if the app is in the background. After API 27, it will be fired the moment the app is put in the foreground. On iOS, it will be ignored if the app is in the background. If the app is in the foreground, this will trigger the `push()` callback with "Hello World" as the argument. -TIP: You can determine the the type of push that has been received in your `push()` callback by calling `Display.getInstance().getProperty("pushType")`. This will return a String of the push type. For example: in this case `Display.getInstance().getProperty("pushType")` will return "2". +TIP: You can determine the kind of push that has been received in your `push()` callback by calling `Display.getInstance().getProperty("pushType")`. This will return a String of the push type. For example: in this case `Display.getInstance().getProperty("pushType")` will return "2." -==== Example Push Type 3 +==== Example push type 3 **Push Type 3; Message Body `Hello World;{"from":"Jim", "content":"Hello World"}`** @@ -241,13 +241,13 @@ If the app is in the background, then the alert message will be posted to the us .Push type 3 shows the alert message (the portion before the first ";"). image::img/push-type-3-simulator.png[Push type 3,scaledwidth=30%] -This push will result in your `push()` callback being fired twice; once with the alert message, and once with the hidden content. When it's fired with the alert message, `Display.getInstance().getProperty("pushType")` will report a type of "1". When it's fired with the JSON hidden content, it will report a push type of "2". +This push will result in your `push()` callback being fired twice; once with the alert message, and once with the hidden content. When it's fired with the alert message, `Display.getInstance().getProperty("pushType")` will report a kind of "1." When it's fired with the JSON hidden content, it will report a push kind of "2." -==== Example Push Type 4 +==== Example push type 4 **Push Type 4; Message Body "Hello World;I am saying hello"** -Push type 4 specifies a title and a message body. In this example, alert title will be "Hello World", and the body will by "I am saying hello". +Push type 4 specifies a title and a message body. In this example, alert title will be `Hello World`, and the body will by `I am saying hello`. .Push type 4 "Hello World" message in simulator. image::img/push-type-4-simulator-paused.png[Push popup when app paused,scaledwidth=30%] @@ -265,38 +265,38 @@ With this push type, the `push()` callback will be fired if the user taps/opens NOTE: On some platforms, the argument of the `push()` callback will include the "body" portion of the payload, and in other platforms it will include the full "Title;Body" payload. -==== Example Push Type 5 +==== Example push type 5 **Push Type 5; Message Body "Hello World"** -Push type 5 will behave identically to push type 1, except that the notification won't make any sound on the device. On some platforms, `Display.getInstance().getProperty("pushType")` will report a push type of "1", when it receives a push of type 5. +Push type 5 will behave identically to push type 1, except that the notification won't make any sound on the device. On some platforms, `Display.getInstance().getProperty("pushType")` will report a push kind of "1," when it receives a push of type 5. -==== Example Push Type 100 +==== Example push type 100 **Push Type 100; Message Body "5"** -Push type 100 expects an integer in the message body. This is interpreted as the badge that should be set on the app. This is currently supported on Windows and iOS. +Push type 100 expects an integer in the message body. This is interpreted as the badge that should be set on the app. This is supported on Windows and iOS. .Push type 100 on iOS, setting the badge to "5" image::img/push-type-100-example-5-ios.png[Push type 100 with badge set to 5 on iOS,scaledwidth=30%] -Push type 100 should not trigger the `push()` callback. +Push type 100 shouldn't trigger the `push()` callback. -==== Example Push Type 101 +==== Example push type 101 **Push Type 101; Message Body "5 Hello World"** -Push type 101 combines a badge with an alert message. The badge number should be the first thing in the payload, followed by a space, and the remainder is the alert message. +Push type 101 combines a badge with an alert message. The badge number should be the first thing in the payload, followed by a space, and the rest is the alert message. -On platforms that don't support badges, Push type 101 will behave exactly as push type 1, except that the badge prefix will be stripped from the message. +On platforms that don't support badges, Push type 101 will behave as push type 1, except that the badge prefix will be stripped from the message. The `push()` callback will be called if the user taps the notification. `Display.getInstance().getProperty("pushType")` will return "1" for this type. -==== Example Push Type 102 +==== Example push type 102 **Push Type 102; Message Body "5;Hello World;you've 5 new tasks"** -Push type 102 allows you to set the badge, title, and body in a single payload. The first segment (before the first semicolon) is the badge value, the second segment is the notification title, and the remainder after the second semicolon is the body that will be displayed to the user. +Push type 102 allows you to set the badge, title, and body in a single payload. The first segment (before the first semicolon) is the badge value, the second segment is the notification title, and the rest after the second semicolon is the body that will be displayed to the user. On platforms that don't support badges or titles, the payload will fall back to the features that are supported. When your `push()` callback is invoked it will receive the `title;body` portion ("Hello World;you've 5 new tasks" in this example). @@ -308,21 +308,21 @@ The badge number can be set thru code as well, this is useful if you want the ba To do this you've two methods in the https://www.codenameone.com/javadoc/com/codename1/ui/Display.html[Display] class: `isBadgingSupported()` and `setBadgeNumber(int)`. Notice that even if `isBadgingSupported` will return `true`, it won't work unless you activate push support! -To truly utilize this you might need to disable the clearing of the badges on startup which you can do with the +To truly use this you might need to disable the clearing of the badges on startup which you can do with the build hint `ios.enableBadgeClear=false`. **** -=== Rich Push Notifications +=== Rich push notifications -Rich push notifications refer to push notifications that include functionality above and beyond simply displaying an alert message to the user. Codename One's support for rich notifications includes image attachments and push actions. +Rich push notifications refer to push notifications that include functionality above and beyond displaying an alert message to the user. Codename One's support for rich notifications includes image attachments and push actions. -==== Image Attachment Support +==== Image attachment support -When you attach an image to a push notification, it will appear as a large image in the push notification on the user's device if that device supports it. iOS supports image attachments in iOS 10, Android supports them in API 26. The Javascript port doesn't currently support image attachments. If a platform that doesn't support image attachments receives a push notification with an image attachment, it will ignore it. +When you attach an image to a push notification, it will appear as a large image in the push notification on the user's device if that device supports it. iOS supports image attachments in iOS 10, Android supports them in API 26. The Javascript port doesn't support image attachments. If a platform that doesn't support image attachments receives a push notification with an image attachment, it will ignore it. -Push type "99" is used to send rich push notifications. it's sort of a "meta" push type, or a "container", as it can be used to send any of the other push types, but to attach additional content, such as image attachments. +Push type "99" is used to send rich push notifications. it's sort of a "meta" push type, or a "container," as it can be used to send any of the other push types, but to attach more content, such as image attachments. -The message body should be an XML string. A minimal example of a push type 99 that *actually* sends a push type 1, which message "Hello World", but with an attached image is: +The message body should be an XML string. A minimal example of a push type 99 that *actually* sends a push type 1, which message "Hello World," but with an attached image is: [source,xml] ---- @@ -343,7 +343,7 @@ int pushType = builder.getType(); // Will be 99 when image/category metadata is You can then send `payload` as the message body with `pushType` as the type through the REST API, the `Push` helper, or any of the other examples below. -IMPORTANT: The image URL *must* be a secure URL (that's: start with "https:" and not "http:", otherwise, iOS will simply ignore it. +IMPORTANT: The image URL *must* be a secure URL (that's: start with "https:" and not "http:," otherwise, iOS will ignore it. .Push type 99 with attached image in simulator. image::img/push-type-99-hello-world-simulator-paused.png[Push popup when app paused,scaledwidth=30%] @@ -358,7 +358,7 @@ NOTE: The image will be shown if you press and pull down on the notification. Wh The `push()` callback will receive "Hello World" as its argument and `Display.getInstance().getProperty("pushType")` will return "1" in this example. -You can access additional information about the push content using the `com.codename1.push.PushContent` class, as follows: +You can access more information about the push content using the `com.codename1.push.PushContent` class, as follows: [source,java] ---- @@ -377,11 +377,11 @@ public void push(String message) { WARNING: Make sure to call `PushContent.get()` *once* inside your `push()` callback, and store the return value. `PushContent.get()` works like a queue of size=1, and it pops off the item from the front of the queue when it's called. If you call it twice, the second time will return `null`. -`PushContent` exposes all of the fields that might accompany a rich notification: `getTitle()`/`getBody()` for the visible text, `getMetaData()` for hidden metadata (such as the second segment of a type 3 payload), `getImageUrl()` for attachments, `getCategory()`/`getActionId()` for actions, and `getTextResponse()` for any user-entered reply text. +`PushContent` exposes all the fields that might accompany a rich notification: `getTitle()`/`getBody()` for the visible text, `getMetaData()` for hidden metadata (such as the second segment of a type 3 payload), `getImageUrl()` for attachments, `getCategory()`/`getActionId()` for actions, and `getTextResponse()` for any user-entered reply text. -==== Notification Actions +==== Notification actions -When you include actions in a push notification, the user will be presented with buttons as part of the notification on supported platforms. For example: if the notification is intended to invite the user to an event, you might want to include buttons/actions like "Attending", "Not Attending", "Maybe", so that the user can respond quickly to the notification and not necessarily have to open your app. +When you include actions in a push notification, the user will be presented with buttons as part of the notification on supported platforms. For example: if the notification is intended to invite the user to an event, you might want to include buttons/actions like `Attending`, `Not Attending`, `Maybe`, so that the user can respond to the notification and not necessarily have to open your app. You can determine whether the user has pressed a particular button on the notification using the `PushContent.getActionId()` method inside your `push()` callback. @@ -410,9 +410,9 @@ public class MyApplication implements PushCallback, PushActionsProvider { public PushActionCategory[] getPushActionCategories() { return new PushActionCategory[]{ new PushActionCategory("invite", new PushAction[]{ - new PushAction("yes", "Yes"), - new PushAction("no", "No"), - new PushAction("maybe", "Maybe"), + new PushAction("yes," "Yes"), + new PushAction("no," "No"), + new PushAction("maybe," "Maybe"), new PushAction("reply", "Reply", null, "Type your response...", "Send") }) @@ -421,11 +421,11 @@ public class MyApplication implements PushCallback, PushActionsProvider { } ---- -In the above example, you create a single category, "invite" that has actions "yes", "no", "maybe", and a "reply" action that prompts the user for text input. A text-input action is enabled by providing either a placeholder, button text, or both when constructing the `PushAction`. +In the above example, you create a single category, "invite" that has actions "yes," "no," "maybe," and a "reply" action that prompts the user for text input. A text-input action is enabled by providing either a placeholder, button text, or both when constructing the `PushAction`. **Sending a Push Notification with "invite" Category** -Now you can test your new category. In the push simulator, you can select Push Type "99", with the message body: +Now you can test your new category. In the push simulator, you can select Push Type "99," with the message body: [source,xml] ---- @@ -469,14 +469,14 @@ public void push(String message) { } ---- -=== Deploying Push-Enabled Apps to Device +=== Deploying Push-Enabled apps to device -So, you've implemented the Push callback in your app, and tested it in the push simulator and it works. If you try to deploy your app to a device, you may be disappointed to discover that your app doesn't seem to be receiving push notifications when installed on the device. This is because each platform has its own bureaucracy and hoops that you've to jump through before they will deliver notifications to your app. Read on to find out how to satisfy their requirements. +you've implemented the Push callback in your app, and tested it in the push simulator and it works. If you try to deploy your app to a device, you may be disappointed to discover that your app doesn't seem to be receiving push notifications when installed on the device. This is because each platform has its own bureaucracy and hoops that you've to jump through before they will deliver notifications to your app. Read on to find out how to meet their requirements. [[push-bureaucracy-android-section]] -==== The Push Bureaucracy - Android +==== The push bureaucracy - Android -TIP: To set the push icon place a 24x24 icon named `ic_stat_notify.png` under the `native/android` folder of the app. The icon can be white with transparency areas +TIP: To set the push icon place a 24×24 icon named `ic_stat_notify.png` under the `native/android` folder of the app. The icon can be white with transparency areas Android Push goes thru Google servers and to do that you need to register with Google to get keys for server usage. Google uses Firebase for its cloud messaging, so you will begin by creating a Firebase project. @@ -490,7 +490,7 @@ This will open a form as shown here: .Enter project name image::img/push-fcm-add-project.png[Enter project name,scaledwidth=30%] -Enter the project name, select your country, read/accept their terms, and press "Create Project". +Enter the project name, select your country, read/accept their terms, and press `Create Project`. Once the project has been created (should take a few seconds), you'll be sent to your new project's dashboard. @@ -502,12 +502,12 @@ Expand the "Grow" section of the left menu bar, then click on the "Cloud Messagi .Expand "Grow" Section and select "Cloud Messaging" image::img/push-fcm-grow-menu.png[Expand Grow section and select Cloud Messaging,scaledwidth=30%] -On the next screen, click on the Android icon where is says "Add an app to get started". +On the next screen, click on the Android icon where is says `Add an app to get started`. .Click on the "Android" icon to add an Android App to the project image::img/push-fcm-enable-notifications.png[Click on the Android icon,scaledwidth=30%] -This will bring you to the "Add Application Form", which visually shows you the remainder of the steps. +This will bring you to the `Add Application Form`, which visually shows you the rest of the steps. Fill in the Android package name with the package name of your project, and the app nickname with your app's name. @@ -518,7 +518,7 @@ image::img/push-fcm-add-app-form.png[Fill in the package name,scaledwidth=30%] Press "Register app" once you've filled in the required fields. -This will expand "Step 2" of this form: "Download config file". +This will expand "Step 2" of this form: `Download config file`. .Download the google-services.json file image::img/push-fcm-download-config-file.png[Download google-services.json,scaledwidth=30%] @@ -542,19 +542,19 @@ The "Server Key" displayed here is the `FCM_SERVER_API_KEY` that you refer to th .Save the Server key for later use. image::img/push-fcm-server-key.png[Save the Server Key for later use,scaledwidth=30%] -NOTE: The Sender ID shown in the above isn't required for your Android app, however it it's helpful/required to support Push notifications in Javascript builds (in Chrome). This value is referred to elsewhere in this document as `GCM_SENDER_ID`. +NOTE: The Sender ID shown in the above isn't required for your Android app, but it's helpful/required to support Push notifications in Javascript builds (in Chrome). This value is referred to elsewhere in this document as `GCM_SENDER_ID`. [[push-bureaucracy-ios-section]] -==== The Push Bureaucracy - iOS +==== The push bureaucracy - iOS -Push on iOS is much harder to handle than the Android version, however you simplified this significantly with the certificate wizard. +Push on iOS is much harder to handle than the Android version, but you simplified this significantly with the certificate wizard. -iOS push needs two additional P12 certificates. +iOS push needs two more P12 certificates. IMPORTANT: Please *notice* that these are *NOT* the signing certificates! + They are *completely different certificates* that have nothing to do with the build process! -The certificate wizard can generate these additional push certificates and do a few other things if you check this flag in the end of the wizard: +The certificate wizard can generate these more push certificates and do a few other things if you check this flag in the end of the wizard: .Enable Push in the certificate wizard image::img/certificate-wizard-enable-push.png[Enable Push in the certificate wizard,scaledwidth=40%] @@ -590,12 +590,12 @@ You can read more about the certificate wizard in the https://www.codenameone.co You can read more about the certificate wizard in the <> //// -==== Historical Note About UWP Push +==== Historical note about UWP push The Codename One UWP target was discontinued in release 7.0.229. Older references to WNS setup, Package SID values, and client secrets are preserved in archived material for teams maintaining legacy code outside the current supported build flow. [[push-bureaucracy-javascript-section]] -==== The Push Bureaucracy - Javascript +==== The push bureaucracy - JavaScript Codename One apps support push in browsers that implement the Web Push API. At time of writing, this list includes: @@ -617,9 +617,9 @@ Where `GCM_SENDER_ID` is your GCM_SENDER_ID. NOTE: Push support requires that your app be served over https with a valid SSL certificate. It won't work with the "preview" version of your app. You'll need to download the.zip or.war file and host the file on your own site - with a valid SSL certificate. -=== Sending Push Messages +=== Sending push messages -You can send a push message in many ways for example: from another device or any type of server but there are some values that you will need regardless of the way in which you send the push message. +You can send a push message in many ways for example: from another device or any kind of server but there are some values that you will need regardless of the way in which you send the push message. [source,java] ----- @@ -656,11 +656,11 @@ This allows you to debug the push related functionality without risking the poss private static final boolean ITUNES_PRODUCTION_PUSH = false; ----- -iOS needs a certificate in order to send a push, this allows you to prove to Apples push servers that you're who you claim to be (the author of the app). +iOS needs a certificate to send a push, this allows you to prove to Apples push servers that you're who you claim to be (the author of the app). -IMPORTANT: These aren't the signing certificates and are completely separate from them! +IMPORTANT: These aren't the signing certificates and are separate from them! -You can obtain these two certificates (for development/App Store) through the certificate wizard as <>. +You can get these two certificates (for development/App Store) through the certificate wizard as <>. [source,java] ----- @@ -670,11 +670,11 @@ private static final String ITUNES_DEVELOPMENT_PUSH_CERT = "https://domain.com/l private static final String ITUNES_DEVELOPMENT_PUSH_CERT_PASSWORD = "DevPassword"; ----- -==== Sending a Push Message From Codename One +==== Sending a push message from Codename One -While normally sending a push message to a device should involve a server code there might be cases (for example: instant messaging/social) where initiating a push from one client to another makes sense. +While sending a push message to a device should involve a server code there might be cases (for example: instant messaging/social) where initiating a push from one client to another makes sense. -To simplify these use cases you added the https://www.codenameone.com/javadoc/com/codename1/push/Push.html[Push] API. To use the `Push` API you need the device key of the destination device to which you want to send the message. You can get that value from the `Push.getPushKey()` method. Notice that you need that value from the *destination device* and not the local device! +To simplify these use cases Codename One provides the https://www.codenameone.com/javadoc/com/codename1/push/Push.html[Push] API. To use the `Push` API you need the device key of the destination device to which you want to send the message. You can get that value from the `Push.getPushKey()` method. Notice that you need that value from the *destination device* and not the local device! To send a message to another device use: @@ -702,17 +702,17 @@ new Push(PUSH_TOKEN, builder.build(), deviceKey) .send(); ----- -The "builder" style API used in the above sample was added post Codename One 3.6 to facilitate the addition of new Push services. If you're building against Codename one 3.6 or earlier, you should use the static `Push.sendPushMessage()` instead as shown below: +The "builder" style API used in the above sample was added post Codename One 3.6 to ease the addition of new Push services. If you're building against Codename one 3.6 or earlier, you should use the static `Push.sendPushMessage()` instead as shown below: [source,java] ---- -Push.sendPushMessage(PUSH_TOKEN, "Hello World", +Push.sendPushMessage(PUSH_TOKEN, `Hello World`, ITUNES_PRODUCTION_PUSH, FCM_SERVER_API_KEY, cert, pass, 1, deviceKey)); ---- -This will send the push message "Hello World" to the device with the key `deviceKey`. The `1` argument represents the standard push message type, which you discussed <>. +This will send the push message "Hello World" to the device with the key `deviceKey`. The `1` argument represents the standard push message type, which you discussed <>. -==== Sending Push Message From A Java or Generic Server +==== Sending push message from a Java or generic server Sending a push message from the server is a more elaborate affair and might require sending push messages to many devices in a single batch. @@ -762,20 +762,20 @@ int c = connection.getResponseCode(); Notice that you can send a push to 500 devices. To send in larger batches you need to split the push requests into 500 device batches. -===== Server JSON Responses +===== Server JSON responses The push servers send responses in JSON form. it's crucial to parse and manage those as they might contain important information. If there is an error that isn't fatal such as quota exceeded etc. you will get an error message like this: -[source,javascript] +[source,JavaScript] ----- {"error":"Error message"} ----- A normal response, will be an array with results: -[source,javascript] +[source,JavaScript] ----- [ {"id"="deviceId","status"="error","message"="Invalid Device ID"}, @@ -787,7 +787,7 @@ A normal response, will be an array with results: ----- -There are many things to notice in the responses above: +many things to notice in the responses above: - If the response contains `status=updateId` it means that the GCM server wants you to update the device id to a new device id. You should do that in the database and avoid sending pushes to the old key - iOS doesn't acknowledge device receipt but it does send a `status=inactive` result which you should use to remove the device from the list of devices diff --git a/docs/developer-guide/The-Components-Of-Codename-One.asciidoc b/docs/developer-guide/The-Components-Of-Codename-One.asciidoc index 13c598f336..5518267690 100644 --- a/docs/developer-guide/The-Components-Of-Codename-One.asciidoc +++ b/docs/developer-guide/The-Components-Of-Codename-One.asciidoc @@ -21,7 +21,7 @@ You can read more about layout managers in the https://www.codenameone.com/manua You can read more about layout managers in the <>. //// -==== Composite Components +==== Composite components Codename One components share a generic inheritance hierarchy. For example, https://www.codenameone.com/javadoc/com/codename1/ui/Button.html[Button] derives from https://www.codenameone.com/javadoc/com/codename1/ui/Label.html[Label] and thus receives all its abilities. @@ -36,9 +36,9 @@ Some components are composites and derive from the https://www.codenameone.com/j [[lead-component-sidebar]] .Lead Component **** -Codename One includes a feature for creating composite components called "lead components". It lets components like `MultiButton` act like a single component while still containing multiple components. +Codename One includes a feature for creating composite components called "lead components." It lets components like `MultiButton` act like a single component while still containing multiple components. -Lead components work by assigning one component as the "leader". That leader determines the style state for every component in the hierarchy. If a `Container` is led by a `Button`, the button determines whether the selected or pressed state applies to the entire hierarchy. +Lead components work by assigning one component as the "leader." That leader determines the style state for every component in the hierarchy. If a `Container` is led by a `Button`, the button determines whether the selected or pressed state applies to the entire hierarchy. This means a single `Component` can contain multiple nested `UIID`s. For example, `MultiButton` has `UIID`s such as `MultiLine1`, which you can customize with APIs such as https://www.codenameone.com/javadoc/com/codename1/components/MultiButton.html#setUIIDLine1-java.lang.String-[setUIIDLine1]. @@ -55,7 +55,7 @@ You can learn more about lead components in <>. === Form -https://www.codenameone.com/javadoc/com/codename1/ui/Form.html[Form] is the top-level container in Codename One. `Form` derives from `Container` and is the element the app shows. Only one form can be visible at any given time. You can get the currently visible `Form` with the code: +https://www.codenameone.com/javadoc/com/codename1/ui/Form.html[Form] is the top-level container in Codename One. `Form` derives from `Container` and is the element the app shows. Only one form can be visible at any given time. You can get the visible `Form` with the code: [source,java] ---- @@ -75,18 +75,18 @@ myForm.getContentPane().add(...); .Form layout graphic image::img/perspective-form-layers.png[Form layout graphic,scaledwidth=20%] -You can see that every `Form` has space allocated for the title area. If you don't set the title, it won't show up, but it will still be there. The same isn't always true for the menu bar, which can vary significantly. Effectively, the section that matters is the content pane, so the form tries to do the right thing by pretending to be the content pane. For example, this isn't always seamless, and sometimes code needs to invoke `getContentPane()` in order to work directly with the container. +You can see that every `Form` has space allocated for the title area. If you don't set the title, it won't show up, but it will still be there. The same isn't always true for the menu bar, which can vary. Effectively, the section that matters is the content pane, so the form tries to do the right thing by pretending to be the content pane. For example, this isn't always seamless, and sometimes code needs to invoke `getContentPane()` to work directly with the container. TIP: A good example for such a case is layout animations. Animating the form might not produce the right results. When in doubt, it's easier to use `getContentPane()` instead of working with the `Form` directly. -As you can see from <>, `Form` has two layers that reside on top of the content pane/title. The first is the layered pane, which allows you to place "always on top" components. The layered pane is added implicitly when you invoke `getLayeredPane()`. +As you can see from <>, `Form` has two layers that live on top of the content pane/title. The first is the layered pane, which allows you to place "always on top" components. The layered pane is added implicitly when you invoke `getLayeredPane()`. -NOTE: You still need to place components using layout managers in order to get them to appear in the right place when using the layered pane. +NOTE: You still need to place components using layout managers to get them to appear in the right place when using the layered pane. [[safe-areas]] -==== Safe Areas +==== Safe areas -Modern phones frequently include display cut-outs, rounded corners, or persistent gesture/navigation indicators that overlap the physical screen edges. These intrusions are no longer limited to iOS—many recent Android devices behave the same way—so Codename One provides a "safe area" API to ensure your UI remains visible regardless of the device. The safe area is the portion of the display that's guaranteed to be unobstructed. +Modern phones often include display cut-outs, rounded corners, or persistent gesture/navigation indicators that overlap the physical screen edges. These intrusions are no longer limited to iOS—many recent Android devices behave the same way—so Codename One provides a "safe area" API to ensure your UI remains visible regardless of the device. The safe area is the portion of the display that's guaranteed to be unobstructed. .Tabs with safe-area padding enabled image::img/safe-area-good.png[Tabs with safe-area padding enabled,scaledwidth=40%] @@ -94,9 +94,9 @@ image::img/safe-area-good.png[Tabs with safe-area padding enabled,scaledwidth=40 .Tabs without safe-area padding image::img/safe-area-missing.png[Tabs without safe-area padding,scaledwidth=40%] -===== Automatic Safe-Area Handling +===== Automatic Safe-Area handling -`Form` calculates the safe region automatically, and core UI components that anchor themselves to the screen edges apply that information without additional work: +`Form` calculates the safe region automatically, and core UI components that anchor themselves to the screen edges apply that information without more work: * `Toolbar` and the status bar placeholder respect the top safe margins so that titles and action buttons remain legible around notches. * `Tabs`, `Sheet`, and `FloatingActionButton` instances mark their internal containers as safe areas so navigation controls are padded above gesture/navigation bars. @@ -104,7 +104,7 @@ image::img/safe-area-missing.png[Tabs without safe-area padding,scaledwidth=40%] This means that many applications will work on devices such as the iPhone X/14 family or Android devices with edge-to-edge displays. You need to intervene when you use custom containers that you position flush against a screen edge or when you perform your own painting. -===== Marking Your Own Containers as Safe +===== Marking your own containers as safe If you create a container that should always stay clear of the unsafe portions of the screen (for example, a bottom navigation bar, an on-screen joystick, or a floating tool palette), enable safe-area padding explicitly: @@ -134,11 +134,11 @@ g.setClip(safe.getX(), safe.getY(), safe.getWidth(), safe.getHeight()); // Custom drawing code that should avoid the notch/gesture areas ---- -The rectangle returned by `Form#getSafeArea()` is updated automatically whenever the OS reports a change (rotation, multitasking gestures, showing or hiding the system navigation area, and so on). In unusual situations where you adjust the safe-area root yourself (for example, when animating a container in from off-screen) you can force a recalculation by calling `Form#setSafeAreaChanged()`. +The rectangle returned by `Form#getSafeArea()` is updated automatically whenever the OS reports a change (rotation, multitasking gestures, showing or hiding the system navigation area, etc.). In unusual situations where you adjust the safe-area root yourself (for example, when animating a container in from off-screen) you can force a recalculation by calling `Form#setSafeAreaChanged()`. -===== Safe-Area Roots and Advanced Layouts +===== Safe-Area roots and advanced layouts -Safe-area padding is calculated relative to a "safe-area root". Forms are roots by default, but you can mark any container as a root using `Container#setSafeAreaRoot(true)` when you need precise control, for example, when preparing a side menu that starts off-screen and slides in: +Safe-area padding is calculated relative to a "safe-area root." Forms are roots by default, but you can mark any container as a root using `Container#setSafeAreaRoot(true)` when you need precise control, for example, when preparing a side menu that starts off-screen and slides in: [source,java] ---- @@ -151,13 +151,13 @@ Marking the drawer as both a root and a safe area prevents a jump the moment it Remember that safe areas apply across platforms. Always verify your screens on actual devices (or in the Codename One simulator with a device skin that exposes cut-outs) to make sure critical UI elements remain inside the padded region. -The second layer is the glass pane which allows you to draw arbitrary things on top of everything. The order in the image is indeed accurate: +The second layer is the glass pane which allows you to draw arbitrary things on top of everything. The order in the image is indeed right: 1. `ContentPane` is lowest 2. `LayeredPane` is second 3. `GlassPane` is painted last -NOTE: it's important to notice that a layered pane is on top of the `ContentPane` and doesn't stretch to the title. A `GlassPane` usually stretches all the way but with a "lightweight" title area, for example, the https://www.codenameone.com/javadoc/com/codename1/ui/Toolbar.html[Toolbar API]. +NOTE: it's important to notice that a layered pane is on top of the `ContentPane` and doesn't stretch to the title. A `GlassPane` stretches all the way but with a "lightweight" title area, for example, the https://www.codenameone.com/javadoc/com/codename1/ui/Toolbar.html[Toolbar API]. The `GlassPane` allows developers to overlay UI on top of existing UI and paint as they see fit. This is useful for things that provide notification but don't want to intrude with application functionality. @@ -165,14 +165,14 @@ NOTE: In earlier versions of Codename One (pre-3.6), `LayeredPane` & `GlassPane` === Dialog -A https://www.codenameone.com/javadoc/com/codename1/ui/Dialog.html[Dialog] is a special kind of `Form` that can occupy a portion of the screen. It also has the additional functionality of the modal `show` method. +A https://www.codenameone.com/javadoc/com/codename1/ui/Dialog.html[Dialog] is a special kind of `Form` that can occupy a portion of the screen. It also has the more functionality of the modal `show` method. When showing a dialog you've two basic options: modeless and modal: - Modal dialogs (the default) block the current EDT thread until the dialog is dismissed. To understand how they do it, read about `invokeAndBlock`. + -Modal dialogs are an extremely useful way to prompt the user since the code can assume the user responded in the next line of execution. This promotes a linear and intuitive way of writing code. +Modal dialogs are an useful way to prompt the user since the code can assume the user responded in the next line of execution. This promotes a linear and intuitive way of writing code. -- Modeless dialogs return immediately, so a call to show such a dialog can't assume anything in the next line of execution. This is useful for features such as progress indicators where you aren't waiting for user input. +- Modeless dialogs return, so a call to show such a dialog can't assume anything in the next line of execution. This is useful for features such as progress indicators where you aren't waiting for user input. For example: a modal dialog can be expressed as such: @@ -187,7 +187,7 @@ if(Dialog.show("Click Yes Or No", "Select one", "Yes", "No")) { Notice that during the `show` call above the execution of the next line was "paused" until you got a response from the user and once the response was returned you could proceed directly. -IMPORTANT: All usage of `Dialog` must be within the Event Dispatch Thread (the default thread of Codename One). This is especially true for modal dialogs. The `Dialog` class knows how to "block the EDT" without blocking it. +IMPORTANT: All usage of `Dialog` must be within the Event Dispatch Thread (the default thread of Codename One). This is true for modal dialogs. The `Dialog` class knows how to "block the EDT" without blocking it. // HTML_ONLY_START To learn more about `invokeAndBlock` which is the workhorse behind the modal dialog functionality check out https://www.codenameone.com/manual/edt.html[the EDT section]. @@ -197,7 +197,7 @@ To learn more about `invokeAndBlock` which is the workhorse behind the modal dia To learn more about `invokeAndBlock` which is the workhorse behind the modal dialog functionality check out <>. //// -The `Dialog` class contains many static helper methods to quickly show user notifications, but also allows a +The `Dialog` class contains many static helper methods to show user notifications, but also allows a developer to create a `Dialog` instance, add information to its content pane and show the dialog. TIP: Dialogs contain a `ContentPane` like `Form`. @@ -219,7 +219,7 @@ image::img/components-dialog-modal-south.png[Custom Dialog in the south position TIP: You can turn the code above to a modless `Dialog` by flipping the boolean `true` argument to `false`. -You can position a `Dialog` absolutely by determining the space from the edges for example: with this code you can occupy the bottom portion of the screen: +You can position a `Dialog` by determining the space from the edges for example: with this code you can occupy the bottom portion of the screen: [source,java] ---- @@ -230,18 +230,18 @@ d.show(hi.getHeight() / 2, 0, 0, 0); ---- .Custom Dialog positioned absolutely -image::img/components-dialog-modal-bottom-half.png[Custom Dialog positioned absolutely,scaledwidth=20%] +image::img/components-dialog-modal-bottom-half.png[Custom Dialog positioned,scaledwidth=20%] NOTE: `hi` is the name of the parent `Form` in the sample above. -==== Styling Dialogs +==== Styling dialogs it's important to style a `Dialog` using https://www.codenameone.com/javadoc/com/codename1/ui/Dialog.html#getDialogStyle--[getDialogStyle()] or https://www.codenameone.com/javadoc/com/codename1/ui/Dialog.html#setDialogUIID-java.lang.String-[setDialogUIID] methods rather than styling the dialog object directly. -The reason for this is that the `Dialog` is a `Form` that takes up the whole screen. The `Form` that's visible behind the `Dialog` is rendered as a screenshot. So customizing the actual `UIID` of the `Dialog` won't produce the desired results. +The reason for this is that the `Dialog` is a `Form` that takes up the whole screen. The `Form` that's visible behind the `Dialog` is rendered as a screenshot. customizing the actual `UIID` of the `Dialog` won't produce the desired results. -==== Tint and Blurring +==== Tint and blurring By default a `Dialog` uses a platform specific tint color when it's showing for example: notice the background in the image below is tinted: @@ -304,7 +304,7 @@ hi.setTintColor(0); image::img/components-dialog-blur-no-tint.png[The blur effect is more pronounced when the tint is disabled,scaledwidth=20%] -==== Popup Dialog +==== Popup dialog A popup dialog is a common mobile paradigm showing a `Dialog` that points at a specific component. it's a standard `Dialog` that's shown in a unique way: @@ -321,7 +321,7 @@ image::img/components-dialog-popup.png[Popup Dialog,scaledwidth=20%] The popup dialog accepts a https://www.codenameone.com/javadoc/com/codename1/ui/Component.html[Component] or https://www.codenameone.com/javadoc/com/codename1/ui/geom/Rectangle.html[Rectangle] to point at and handles the rest. -===== Styling The Arrow Of The Popup Dialog +===== Styling the arrow of the popup dialog When Codename One was young you needed a popup arrow implementation but your low-level graphics API was pretty basic. As a workaround you created a version of the 9-piece image border that supported pointing arrows at a component. @@ -335,12 +335,12 @@ The new `RoundRectBorder` support works by setting the track component property If you still need deeper customization of the arrow you can still use the old 9-piece border functionality illustrated below. -====== Legacy 9-Piece Border Arrow +====== Legacy 9-Piece Border arrow One of the harder aspects of a popup dialog is the construction of the theme elements required for arrow styling. To get that sort of behavior you will need a custom image border and 4 arrows pointing in each direction that will be overlaid with the border. NOTE: The sizes of the arrow images should be similarly proportioned and fit within the image borders whitespace. -The block image of the dialog should have empty pixels in the sides to reserve space for the arrow. For example: if the arrows are all 32x32 pixels then the `PopupDialog` image should have 32 pixels of transparent pixels around it. +The block image of the dialog should have empty pixels in the sides to reserve space for the arrow. For example: if the arrows are all 32×32 pixels then the `PopupDialog` image should have 32 pixels of transparent pixels around it. You will need to define the following theme constants for the arrow to work: @@ -362,16 +362,16 @@ For example, there is another definition to those terms: A modal dialog blocks a modeless dialog "floats" on top of the UI. In that sense, all dialogs in Codename One are modal; they block the parent form since they are effectively forms -that show the "parent" in their background. https://www.codenameone.com/javadoc/com/codename1/components/InteractionDialog.html[InteractionDialog] has an API that's similar to the https://www.codenameone.com/javadoc/com/codename1/ui/Dialog.html[Dialog] API +that show the "parent" in their background. https://www.codenameone.com/javadoc/com/codename1/components/InteractionDialog.html[InteractionDialog] has an API that's like the https://www.codenameone.com/javadoc/com/codename1/ui/Dialog.html[Dialog] API but, unlike dialog, it never blocks anything. Neither the calling thread nor the UI. -NOTE: `InteractionDialog` isn't a `Dialog` since it doesn't share the same inheritance hierarchy. For example, it acts and "feels" like a `Dialog` despite the fact that it's a `Container` in the `LayeredPane`. +NOTE: `InteractionDialog` isn't a `Dialog` since it doesn't share the same inheritance hierarchy. For example, it acts and "feels" like a `Dialog` although it's a `Container` in the `LayeredPane`. `InteractionDialog` is a container that's positioned within the layered pane. Notice that because of that design, you can have one such dialog at the moment and, if you add something else to the layered pane, you might run into trouble. -Using the interaction dialog is pretty trivial and similar to dialog: +Using the interaction dialog is pretty trivial and like dialog: [source,java] ---- @@ -390,7 +390,7 @@ image::img/components-interaction-dialog.png[Interaction Dialog,scaledwidth=20%] This will show the dialog on the right hand side of the screen, which is pretty useful for a floating in place dialog. -NOTE: The `InteractionDialog` can be shown at absolute or popup locations. This is inherent to its use case which is "non-blocking". When using this component you need to be aware of its location. +NOTE: The `InteractionDialog` can be shown at absolute or popup locations. This is inherent to its use case which is "non-blocking." When using this component you need to be aware of its location. To make popup behaviour feel natural on touch devices you can call `setDisposeWhenPointerOutOfBounds(true)` so the dialog auto-dismisses as soon as the user taps outside the title or content area. Internally the dialog listens for pointer pressed/released events and will call `dispose()` for you when the interaction happens beyond its bounds, so you no longer need to wire that logic manually. @@ -401,7 +401,7 @@ Finally, recent updates added animation toggles so you can fine-tune presentatio [[label-section]] === Label -https://www.codenameone.com/javadoc/com/codename1/ui/Label.html[Label] represents a text, icon or both. `Label` is also the base class of `Button` which in turn is the base class for `RadioButton` & `CheckBox`. Thus the functionality of the `Label` class extends to all of these components. +https://www.codenameone.com/javadoc/com/codename1/ui/Label.html[Label] represents a text, icon, or both. `Label` is also the base class of `Button` which in turn is the base class for `RadioButton` & `CheckBox`. Thus the functionality of the `Label` class extends to all these components. `Label` text can be positioned in one of 4 locations as such: @@ -425,9 +425,9 @@ image::img/components-label-text-position.png[Label positions,scaledwidth=20%] TIP: <> supports many lines with a single label, notice that it does carry a performance penalty for this functionality. -Labels support tickering and the ability to end with "..." if there isn't enough space to render the label. Developers can determine the placement of the label relatively to its icon in a few powerful ways. +Labels support tickering and the ability to end with "..." if there isn't enough space to render the label. Developers can determine the placement of the label to its icon in a few powerful ways. -==== Label Gap +==== Label gap The gap between the label text & the icon defaults to 2 pixels due to legacy settings. The `setGap` method of `Label` accepts a gap size in pixels. @@ -435,11 +435,11 @@ Two pixels is low for most cases & it's hard to customize for each `Label`. You can use the theme constant `labelGap` which is a floating point value you can specify in millimeters that will allow you to determine the default gap for a label. You can also customize this manually using the method `Label.setDefaultGap(int)` which determines the default gap in pixels. -==== Autosizing Labels +==== Autosizing labels -One of the common requests you received over the years is a way to let text "fit" into the allocated space so the font will match almost exactly the width available. In some designs this is important but it's also tricky. Measuring the width of a String is a surprisingly expensive operation on some OS's. Unfortunately, there is no other way other than trial & error to find the "best size". +One of the common requests you received over the years is a way to let text "fit" into the allocated space so the font will match almost the width available. In some designs this is important but it's also tricky. Measuring the width of a String is a expensive operation on some OS's., there is no other way other than trial & error to find the "best size." -Still despite the fact that something is "slow" you might still want to use it for some cases, this isn't something you should use in a renderer, infinite scroll etc. and recommend minimizing the usage of this feature as much as possible. +Still although something is "slow" you might still want to use it for some cases, this isn't something you should use in a renderer, infinite scroll etc. and recommend minimizing the usage of this feature as much as possible. This feature is applicable to `Label` and its subclasses (for example: `Button`), with components such as `TextArea` (for example: `SpanButton`) the choice between shrinking and line break would require some complex logic. @@ -488,7 +488,7 @@ NOTE: The semantic difference between `TextField` & `TextArea` dates back to the Because it lacks the blinking cursor capability `TextArea` is often used as a multi-line label and is used internally in `SpanLabel`, `SpanButton` etc. -TIP: A common use case is to have an important text component in edit mode immediately as you enter a `Form`. Codename One forms support this exact use case thru the https://www.codenameone.com/javadoc/com/codename1/ui/Form.html#setEditOnShow-com.codename1.ui.TextArea-[Form.setEditOnShow(TextArea)] method. +TIP: A common use case is to have an important text component in edit mode as you enter a `Form`. Codename One forms support this exact use case thru the https://www.codenameone.com/javadoc/com/codename1/ui/Form.html#setEditOnShow-com.codename1.ui.TextArea-[Form.setEditOnShow(TextArea)] method. `TextField` & `TextArea` support constraints for various types of input such as `NUMERIC`, `EMAIL`, `URL`, etc. Those usually @@ -575,17 +575,17 @@ private void automoveToNext(final TextField current, final TextField next) { Notice you can invoke `stopEditing(Runnable)` where you receive a callback as editing is stopped. -==== The Virtual Keyboard +==== The virtual keyboard -A common misconception for developers is assuming the virtual keyboard represents "keys". For example: developers often override the "keyEvent" callbacks which are invoked for physical keyboard typing and expect those to occur with a virtual keyboard. +A common misconception for developers is assuming the virtual keyboard represents "keys." For example: developers often override the "keyEvent" callbacks which are invoked for physical keyboard typing and expect those to occur with a virtual keyboard. -This isn't the case since a virtual keyboard is a different beast. With a virtual keyboard characters typed might produce a completely different output due to autocorrect. Some keyboards don't even have "keys" in the traditional sense or don't type them in the traditional sense (for example: swiping). +This isn't the case since a virtual keyboard is a different beast. With a virtual keyboard characters typed might produce a different output due to autocorrect. Some keyboards don't even have "keys" in the traditional sense or don't type them in the traditional sense (for example: swiping). TIP: The constraint property for the `TextField`/`TextArea` is crucial for a virtual keyboard. TIP: When working with a virtual keyboard it's important that the parent `Container` for the `TextField`/`TextArea` is scrollable. Otherwise the component won't be reachable or the UI might be distorted when the keyboard appears. -===== Action Button Client Property +===== Action Button client property By default, the virtual keyboard on Android has a "Done" button, you can customize it to be a search icon, a send icon, or a go icon using a hint such as this: @@ -599,9 +599,9 @@ goTextField.putClientProperty("goButton", Boolean.TRUE); This will adapt the icon for the action on the keys. -===== Next and Done on iOS +===== Next and done on iOS -You try to hide a lot of the platform differences in Codename One, input is **** different between OS's. A common reliance is the ability to send the "Done" event when the user presses the #Done# button. Unfortunately this button doesn't always exist for example: if there is an #Enter# button (due to multiline input) or if there is a #Next# button in that place. +You try to hide a lot of the platform differences in Codename One, input is **** different between OS's. A common reliance is the ability to send the "Done" event when the user presses the #Done# button. This button doesn't always exist for example: if there is an #Enter# button (due to multiline input) or if there is a #Next# button in that place. To make the behavior more uniform you slightly customized the iOS keyboard as such: @@ -624,7 +624,7 @@ This will hide the toolbar for that given field. NOTE: You can customize the color of the #Done# button in the toolbar by setting the `ios.doneButtonColor` display property. For example: To change the color to red, you could do `Display.getInstance().setProperty("ios.doneButtonColor", String.valueOf(0xff0000))`. `@since 5.0` -==== Clearable Text Field +==== Clearable text field iOS has a convention where an X can be placed after the text field to clear it. Some Android apps have it but there is no native support for that as of this writing. @@ -690,11 +690,11 @@ TextComponent t = new TextComponent(). The code is pretty self-explanatory and more convenient than typical setters/getters. It automatically handles the floating hint style of animation when running on Android. -==== Error Handling +==== Error handling -The validator class supports text component and it should "work". But the cool thing is that it uses the material design convention for error handling! +The validator class supports text component and it should "work." But the cool thing is that it uses the material design convention for error handling! -So if you add to the sample above a `Validator`: +if you add to the sample above a `Validator`: [source,java] ---- @@ -726,7 +726,7 @@ TextComponent tc = new TextComponent(). ==== InputComponent and PickerComponent -To keep the code common and generic you use the `InputComponent` abstract base class and derive the other classes from that. `PickerComponent` is currently the other option. +To keep the code common and generic you use the `InputComponent` abstract base class and derive the other classes from that. `PickerComponent` is the other option. A picker can work with your existing sample using code like this: @@ -761,16 +761,16 @@ image::img/pixel-perfect-text-field-picker-android.png[And in Android,scaledwidt The one tiny thing you should notice with the `PickerComponent` is that you don't construct the picker component using `new PickerComponent()`. Instead you use create methods such as `PickerComponent.createDate(new Date())`. The reason for that's that you've many types of pickers and it wouldn't make sense to have one constructor. -==== Underlying Theme Constants and UIID's +==== Underlying theme constants and UIID's These varying looks are implemented through a combination of layouts, theme constants and UIID's. The most important UIID's are: `TextComponent`, `FloatingHint` & `TextHint`. -There are many theme constants related that can manipulate some pieces of this functionality: +many theme constants related that can manipulate some pieces of this functionality: - `textComponentErrorColor` a hex RGB color which defaults to null in which case this has no effect. When defined this will change the color of the border and label to the given color to match the material design styling. This implements the red border underline in cases of error and the label text color change - `textComponentErrorLineBorderBool` toggles the material-style underline that appears on validation errors. Set it to `false` if you prefer to supply a different border when errors are shown -- `textComponentOnTopBool` toggles the on top mode which makes things look like they do on Android. This defaults to true on Android and false on other OS's. This can also be manipulated through the `onTopMode(boolean)` method in `InputComponent` however the layout will use the theme constant +- `textComponentOnTopBool` toggles the on top mode which makes things look like they do on Android. This defaults to true on Android and false on other OS's. This can also be manipulated through the `onTopMode(boolean)` method in `InputComponent`, but the layout will use the theme constant - `textComponentAnimBool` toggles the animation mode which again can be manipulated by a method in `InputComponent`. If you want to keep the UI static without the floating hint effect set this to false. Notice this defaults to true on Android @@ -781,11 +781,11 @@ There are many theme constants related that can manipulate some pieces of this f [[button-section]] === Button -https://www.codenameone.com/javadoc/com/codename1/ui/Button.html[Button] is a subclass of `Label` and as a result it inherits all of its functionality, specifically icon placement, tickering, etc. +https://www.codenameone.com/javadoc/com/codename1/ui/Button.html[Button] is a subclass of `Label` and as a result it inherits all its functionality, specifically icon placement, tickering, etc. -Button adds to the mix some additional states such as a pressed `UIID` state and pressed icon. +Button adds to the mix some more states such as a pressed `UIID` state and pressed icon. -NOTE: There are additional icon states in `Button` such as rollover and disabled icon. +NOTE: There are more icon states in `Button` such as rollover and disabled icon. `Button` also exposes some functionality for subclasses specifically the `setToggle` method call which has no meaning when invoked on a `Button` but has a lot of implications for `CheckBox` & `RadioButton`. @@ -806,7 +806,7 @@ b.addActionListener((e) -> Log.p("Clicked")); .Simple button in the iOS styling, notice iOS doesn't have borders on buttons... image::img/components-button.png[Simple button in the iOS styling, notice iOS doesn't have borders on buttons...,scaledwidth=40%] -Such a button can be styled to look like a link using code like this or simply by making these settings in the theme and using code such as `btn.setUIID("Hyperlink")`. +Such a button can be styled to look like a link using code like this or by making these settings in the theme and using code such as `btn.setUIID("Hyperlink")`. [source,java] ---- @@ -821,7 +821,7 @@ b.addActionListener((e) -> Log.p("Clicked")); .Button styled to look like a link image::img/components-link-button.png[Button styled to look like a link,scaledwidth=40%] -==== Uppercase Buttons +==== Uppercase buttons Buttons on Android's material design UI use upper case styling which isn't the case for iOS. To solve this you've the method `setCapsText(boolean)` in `Button` which has the corresponding `isCapsText`, `isCapsTextDefault` & `setCapsTextDefault(boolean)`. This is pretty core to Codename One so to prevent this from impacting everything unless you explicitly invoke `setCapsText(boolean)` the default value of `true` will apply when the UIID is `Button`, `RaisedButton` or for the built-in `Dialog` buttons. @@ -831,7 +831,7 @@ You also have a theme constant: `capsButtonTextBool`. This constant controls cap Raised button is a style of button that's available on Android and used to highlight an important action within a form. To confirm with the material design UI guidelines you might want to leverage a raised button UI element on Android but use a regular button everywhere else. -First you need to know whether a raised button exists in the theme. So on Android this will return true but on other OS's it will return false. A potential future update might make another platform true based on UI guidelines in other OS's. +First you need to know whether a raised button exists in the theme. on Android this will return true but on other OS's it will return false. A potential future update might make another platform true based on UI guidelines in other OS's. For this purpose you've got the theme constant `hasRaisedButtonBool` which will return true on Android but will be false elsewhere. You can use it like this: @@ -847,7 +847,7 @@ To enable this you've the `RaisedButton` UIID that derives from `Button` and wil .Raised and flat button in simulator image::img/raised-flat-buttons.png[Raised and flat button in simulator,scaledwidth=40%] -Notice that you can easily customize the colors of these buttons now since the border respects user colors... +Notice that you can customize the colors of these buttons now since the border respects user colors... In this case I set the background color to purple and the foreground to white: @@ -864,15 +864,15 @@ f.add(r); f.show(); ---- -==== Ripple Effect +==== Ripple effect The ripple effect in material design highlights the location of the finger and grows as a circle to occupy the full area of the component as the user presses the button. you've the ability to perform a ripple effect by darkening the touched area and growing that in a quick animation. -Ripple effect can be applied to any component but you currently have it turned on for buttons on Android which also applies to things like title commands, side menu elements etc. This might not apply at this moment to lead components like multi-buttons but that might change in the future. +Ripple effect can be applied to any component but you have it turned on for buttons on Android which also applies to things like title commands, side menu elements etc. This might not apply at this moment to lead components like multi-buttons but that might change in the future. -`Component` has a property to enable the ripple effect `setRippleEffect(boolean)` and the corresponding `isRippleEffect()`. You can turn it on or off individually in the component level. For example, `Button` has static `setButtonRippleEffectDefault(boolean)` and `isButtonRippleEffectDefault()`. These allow you to define the default behavior for all the buttons and that can be configured through the theme constant `buttonRippleBool` which is currently on by default on the native Android theme. +`Component` has a property to enable the ripple effect `setRippleEffect(boolean)` and the corresponding `isRippleEffect()`. You can turn it on or off individually in the component level. For example, `Button` has static `setButtonRippleEffectDefault(boolean)` and `isButtonRippleEffectDefault()`. These allow you to define the default behavior for all the buttons and that can be configured through the theme constant `buttonRippleBool` which is on by default on the native Android theme. === CheckBox/RadioButton @@ -911,13 +911,13 @@ Both of these components can be displayed as toggle buttons (see the toggle butt ==== Toggle Button -A toggle button is a button that's pressed and stays pressed. When a toggle button is pressed again it's released from the pressed state. Hence the button has a selected state to indicate if it's pressed or not exactly like the `CheckBox`/`RadioButton` components in Codename One. +A toggle button is a button that's pressed and stays pressed. When a toggle button is pressed again it's released from the pressed state. Hence the button has a selected state to show if it's pressed or not like the `CheckBox`/`RadioButton` components in Codename One. -To turn any `CheckBox` or `RadioButton` to a toggle button use the `setToggle(true)` method. Alternatively you can use the static `createToggle` method on both `CheckBox` and `RadioButton` to create a toggle button directly. +To turn any `CheckBox` or `RadioButton` to a toggle button use the `setToggle(true)` method. Or you can use the static `createToggle` method on both `CheckBox` and `RadioButton` to create a toggle button directly. IMPORTANT: Invoking `setToggle(true)` implicitly converts the `UIID` to `ToggleButton` unless it was changed by the user from its original default value. -You can easily convert the sample above to use toggle buttons as such: +You can convert the sample above to use toggle buttons as such: [source,java] ---- @@ -958,7 +958,7 @@ https://www.codenameone.com/javadoc/com/codename1/ui/ComponentGroup.html[Compone `ComponentGroup` "restyles" the elements within the group to have a `UIID` that allows you to create a "round border" effect that groups elements together. -The following code adds 4 component groups to a `Container` to demonstrate the various `UIID` changes: +The following code adds 4 component groups to a `Container` to show the various `UIID` changes: [source,java] ---- @@ -983,7 +983,7 @@ Notice the following about the code above and the resulting image: IMPORTANT: By default, `ComponentGroup` does *nothing*. You need to explicitly activate it in the theme by setting a theme property to true. Specifically you need to set `ComponentGroupBool` to `true` for `ComponentGroup` to do something otherwise its a box layout container! The `ComponentGroupBool` flag is true by default in the iOS native theme. -When `ComponentGroupBool` is set to true, the component group will modify the styles of all components placed within it to match the element UIID given to it (GroupElement by default) with special caveats to the first/last/ elements. For example: +When `ComponentGroupBool` is set to true, the component group will change the styles of all components placed within it to match the element UIID given to it (GroupElement by default) with special caveats to the first/last/ elements. For example: 1. If I have one element within a component group it will have the UIID: `GroupElementOnly` 2. If I have two elements within a component group they will have the UIID's `GroupElementFirst`, `GroupElementLast` @@ -1046,7 +1046,7 @@ hi.add(oneLineIconEmblem). .Multiple usage scenarios for the MultiButton image::img/components-multibutton.png[Multiple usage scenarios for the MultiButton,scaledwidth=20%] -==== Styling The MultiButton +==== Styling the MultiButton Since the `MultiButton` is a composite component setting its `UIID` will impact the top level UI. @@ -1103,13 +1103,13 @@ hi.add(d).add(l).add(r).add(c); .The SpanLabel Component image::img/components-spanlabel.png[The SpanLabel Component,scaledwidth=20%] -TIP: `SpanLabel` is significantly slower than `Label`. Recommend using it when there is a genuine need for its functionality. +TIP: `SpanLabel` is slower than `Label`. Recommend using it when there is a genuine need for its functionality. === OnOffSwitch The https://www.codenameone.com/javadoc/com/codename1/components/OnOffSwitch.html[OnOffSwitch] allows you to write an application where the user can swipe a switch between two states (on/off). This is a common UI paradigm in Android and iOS, although it's implemented in a radically different way in both platforms. -This is a rather elaborate component because of its unique design on iOS, but you you're able to accommodate most of the small behaviors of the component into your version, and it seamlessly adapts between the Android style and the iOS style. +This is a rather elaborate component because of its unique design on iOS, but you're able to accommodate most of the small behaviors of the component into your version, and it seamlessly adapts between the Android style and the iOS style. The image below was generated based on the default use of the `OnOffSwitch`: @@ -1132,7 +1132,7 @@ TIP: You can force the Android or iOS mode by using the theme constant `onOffIOS Validation is an inherent part of text input, and the https://www.codenameone.com/javadoc/com/codename1/ui/validation/Validator.html[Validator] class allows that. You can enable validation thru the `Validator` class to add constraints for a specific component. it's also possible to define components that would be enabled/disabled based on validation state and the way in which validation errors are rendered (change the components `UIID`, paint an emblem on top, etc.). A https://www.codenameone.com/javadoc/com/codename1/ui/validation/Constraint.html[Constraint] is an interface -that represents validation requirements. You can define a constraint in Java or use some of the built-in +that represents validation requirements. You can define a constraint in Java or use some built-in constraints such as https://www.codenameone.com/javadoc/com/codename1/ui/validation/LengthConstraint.html[LengthConstraint], https://www.codenameone.com/javadoc/com/codename1/ui/validation/RegexConstraint.html[RegexConstraint], etc. This sample below continues from the place where the <> stopped by adding validation to that code. @@ -1158,7 +1158,7 @@ image::img/validation-regex-masking-1.png[Validation and Regular Expressions,sca === InfiniteProgress -The https://www.codenameone.com/javadoc/com/codename1/components/InfiniteProgress.html[InfiniteProgress] indicator spins an image infinitely to indicate that a background process is still working. +The https://www.codenameone.com/javadoc/com/codename1/components/InfiniteProgress.html[InfiniteProgress] indicator spins an image infinitely to show that a background process is still working. TIP: This style of animation is often nicknamed "washing machine" as it spins endlessly. @@ -1193,9 +1193,9 @@ NOTE: Despite the name of the method `setAnimation` expects a static image that https://www.codenameone.com/javadoc/com/codename1/components/InfiniteScrollAdapter.html[InfiniteScrollAdapter] & https://www.codenameone.com/javadoc/com/codename1/ui/InfiniteContainer.html[InfiniteContainer] allow you to create a scrolling effect that "never" ends with the typical `Container`/`Component` paradigm. -The motivation behind these classes is simple, say you've a lot of data to fetch from storage or from the internet. You can fetch the data in batches and show progress indication while you do this. +The motivation behind these classes is simple, say you've a lot of data to fetch from storage or from the internet. You can fetch the data in batches and show progress sign while you do this. -Infinite scroll fetches the next batch of data dynamically as you reach the end of the `Container`. `InfiniteScrollAdapter` & `InfiniteContainer` represent two similar ways to accomplish that task relatively easily. +Infinite scroll fetches the next batch of data dynamically as you reach the end of the `Container`. `InfiniteScrollAdapter` & `InfiniteContainer` represent two similar ways to do that task. Let start by exploring how you can achieve this UI that fetches data from a webservice: @@ -1265,23 +1265,23 @@ InfiniteScrollAdapter.createInfiniteScroll(hi.getContentPane(), () -> { // <2> }, true); // <6> ---- -<1> Placeholder is essential for the https://www.codenameone.com/javadoc/com/codename1/ui/URLImage.html[URLImage] class which you will discuss at a different place. +<1> Placeholder is essential for the https://www.codenameone.com/javadoc/com/codename1/ui/URLImage.html[URLImage] class which this guide covers at a different place. <2> The `InfiniteScrollAdapter` accepts a runnable which is invoked every time you reach the edge of the scrolling. You used a closure instead of the typical run() method override. <3> This is a blocking call, after the method completes you will have all the data you need. Notice that this method doesn't block the EDT illegally. -<4> If there is no more data you call the `addMoreComponents` method with a false argument. This indicates that there is no additional data to fetch. +<4> If there is no more data you call the `addMoreComponents` method with a false argument. This indicates that there is no more data to fetch. <5> Here you add the actual components to the end of the form. Notice that you *must not* invoke the `add`/`remove` method of `Container`. Those might conflict with the work of the `InfiniteScrollAdapter`. -<6> You pass true to indicate that the data isn't "prefilled" so the method should be invoked immediately when the `Form` is first shown +<6> You pass true to show that the data isn't "prefilled" so the method should be invoked when the `Form` is first shown IMPORTANT: don't violate the EDT in the callback. it's invoked on the event dispatch thread and it's crucial ==== The InfiniteContainer -https://www.codenameone.com/javadoc/com/codename1/ui/InfiniteContainer.html[InfiniteContainer] was introduced to simplify and remove some of the boilerplate of the `InfiniteScrollAdapter`. It takes a more traditional approach of inheriting the Container class to provide its functionality. +https://www.codenameone.com/javadoc/com/codename1/ui/InfiniteContainer.html[InfiniteContainer] was introduced to simplify and remove some boilerplate of the `InfiniteScrollAdapter`. It takes a more traditional approach of inheriting the Container class to provide its functionality. Unlike the `InfiniteScrollAdapter` the `InfiniteContainer` accepts an index and amount to fetch. This is useful for tracking your position but also important since the `InfiniteContainer` also implements #Pull To Refresh# as part of its functionality. @@ -1319,7 +1319,7 @@ InfiniteContainer ic = new InfiniteContainer() { hi.add(BorderLayout.CENTER, ic); ---- -=== List, MultiList, Renderers & Models +=== List, MultiList, renderers & models ==== InfiniteContainer/InfiniteScrollAdapter vs. List/ContainerList @@ -1327,17 +1327,17 @@ Your recommendation is to always go with `Container`, `InfiniteContainer` or `In Recommend avoiding `List` or its subclasses/related classes specifically `ContainerList` & `MultiList`. -NOTE: Recommend replacing `ComboBox` with `Picker` but that's a completely different discussion. +NOTE: Recommend replacing `ComboBox` with `Picker` but that's a different discussion. -A `Container` with ~5000 nested containers within it can perform on par with a `List` and probably exceed its performance when used correctly. +A `Container` with ~5000 nested containers within it can perform on par with a `List` and probably exceed its performance when used. -Larger sets of data are rarely manageable on phones or tablets so the benefits for lists are dubious. +Larger sets of data are manageable on phones or tablets so the benefits for lists are dubious. -In terms of API you found that even experienced developers experienced a great deal of pain when wrangling the Swing styled lists and their stateless approach. +In API you found that even experienced developers experienced a great deal of pain when wrangling the Swing styled lists and their stateless approach. -Since animation, swiping and other capabilities that are so common in mobile are so hard to accomplish with lists you see no actual reason to use them. +Since animation, swiping and other capabilities that are so common in mobile are so hard to do with lists you see no actual reason to use them. -==== Why isn't List Deprecated? +==== Why isn't list deprecated You deprecated `ContainerList` which performs badly and has some inherent complexity issues. `List` has some unique use cases and is still used all over Codename One. @@ -1345,17 +1345,17 @@ You deprecated `ContainerList` which performs badly and has some inherent comple There are cases where using `List` or `MultiList` is justified, they are rarer than usual hence your recommendation. -==== MVC In Lists +==== MVC in lists A Codename One https://www.codenameone.com/javadoc/com/codename1/ui/List.html[List] doesn't contain components, but rather arbitrary data; this seems odd at first but makes sense. If you want a list to contain components, use a Container. The advantage of using a `List` in this way is that you can display it in many ways (for example: fixed focus positions, horizontally, etc.), and that you can have more than a million entries without performance overhead. You can also do some pretty nifty things, like filtering the list on the fly or fetching it dynamically from the Internet as the user scrolls down the list. To achieve these things the list uses two interfaces: https://www.codenameone.com/javadoc/com/codename1/ui/list/ListModel.html[ListModel] and ListCellRenderer. -https://www.codenameone.com/javadoc/com/codename1/ui/List.html[List] model represents the data; its responsibility is to return the arbitrary object within the list at a given offset. Its second responsibility is to notify the list when the data changes, so the list can refresh. +https://www.codenameone.com/javadoc/com/codename1/ui/List.html[List] model represents the data; its responsibility is to return the arbitrary object within the list at a given offset. Its second responsibility is to tell the list when the data changes, so the list can refresh. -TIP: Think of the model as an array of objects that can notify you when it changes. +TIP: Think of the model as an array of objects that can tell you when it changes. -The list renderer is like a rubber stamp that knows how to draw an object from the model, it's called many times per entry in an animated list and must be fast. Unlike standard Codename One components, it's used to draw the entry in the model and is immediately discarded, hence it has no memory overhead, but if it takes too long to process a model value it can be a big bottleneck! +The list renderer is like a rubber stamp that knows how to draw an object from the model, it's called many times per entry in an animated list and must be fast. Unlike standard Codename One components, it's used to draw the entry in the model and is discarded, hence it has no memory overhead, but if it takes too long to process a model value it can be a big bottleneck! TIP: Think of the render as a translation layer that takes the "data" from the model and translates it to a visual representation. @@ -1363,9 +1363,9 @@ This is all generic, but a bit too much for most, doing a list "properly" requir ==== Understanding MVC -Let you recap, what is MVC: +Let's recap, what is MVC: -- #Model# - Represents the data for the component (list), the model can tell you exactly how many items are in it and which item resides at a given offset within the model. This differs from a simple `Vector` (or array), since all access to the model is controlled (the interface is simpler), and unlike a `Vector`/Array, the model can notify you of changes that occur within it. +- #Model# - Represents the data for the component (list), the model can tell you how many items are in it and which item resides at a given offset within the model. This differs from a simple `Vector` (or array), since all access to the model is controlled (the interface is simpler), and unlike a `Vector`/Array, the model can tell you of changes that occur within it. - #View# - The view draws the content of the model. it's a "dumb" layer that has no notion of what is displayed and knows how to draw. It tracks changes in the model (the model sends events) and redraws itself when it changes. - #Controller# - The controller accepts user input and performs changes to the model, which in turn cause the view to refresh. @@ -1375,15 +1375,15 @@ image::img/mvc.png[Image by RegisFrey - Own work Public Domain https://commons.w Codename One's https://www.codenameone.com/javadoc/com/codename1/ui/List.html[List] component uses the MVC paradigm in its implementation. `List` itself is the #Controller# (with a bit of the #View# mixed in). The https://www.codenameone.com/javadoc/com/codename1/ui/list/ListCellRenderer.html[ListCellRenderer] interface is the rest of the #View# and the https://www.codenameone.com/javadoc/com/codename1/ui/list/ListModel.html[ListModel] is (you guessed it by now) the #Model#. -When the list is painted, it iterates over the visible elements in the model and asks the model for the data, it then draws them using the renderer. Notice that because of this both the model and the renderer must be REALLY fast and that's hard. +When the list is painted, it iterates over the visible elements in the model and asks the model for the data, it then draws them using the renderer. Notice that because of this both the model and the renderer must be fast and that's hard. -===== Why is this useful? +===== Why is this useful Since the model is a lightweight interface, it can be implemented by you and replaced in runtime if so desired, this allows many use cases: 1. A list can contain thousands of entries but load the portion visible to the user. Since the model will be queried for the elements that are visible to the user, it won't need to load the large data set into memory until the user starts scrolling down (at which point other elements may be offloaded from memory). -2. A list can cache efficiently. For example: a list can mirror data from the server into local RAM without actually downloading all the data. Data can also be mirrored from storage for better performance and discarded for better memory utilization. +2. A list can cache efficiently. For example: a list can mirror data from the server into local RAM without actually downloading all the data. Data can also be mirrored from storage for better performance and discarded for better memory use. 3. The is no need for state copying. Since renderers allow you to display any object type, the list model interface can be implemented by the application's data structures (for example: persistence/network engine), which would return internal application data structures saving you the need of copying application state into a list specific data structure. Note that this advantage applies with a custom renderer which is pretty difficult to get right. @@ -1394,8 +1394,8 @@ Since the model is a lightweight interface, it can be implemented by you and rep Most of these use cases work best for lists that grow to a larger size, or represent complex data, which is what the list object is designed to do. -==== Important - Lists & Layout Managers -Usually when working with lists, you want the list to handle the scrolling (otherwise it will perform badly). This means you should place the list in a non-scrollable container (no parent can be scrollable), notice that the content pane is scrollable by default, so you should disable that. +==== Important - lists and Layout managers +When working with lists, you want the list to handle the scrolling (otherwise it will perform badly). This means you should place the list in a non-scrollable container (no parent can be scrollable), notice that the content pane is scrollable by default, so you should disable that. it's also recommended to place the list in the `CENTER` location of a https://www.codenameone.com/javadoc/com/codename1/ui/layouts/BorderLayout.html[BorderLayout] to produce the most effective results. for example: @@ -1408,7 +1408,7 @@ form.add(BorderLayout.CENTER, myList); ==== MultiList & DefaultListModel -So after this long start lets show the first sample of creating a list using the https://www.codenameone.com/javadoc/com/codename1/ui/list/MultiList.html[MultiList]. +after this long start lets show the first sample of creating a list using the https://www.codenameone.com/javadoc/com/codename1/ui/list/MultiList.html[MultiList]. The `MultiList` is a preconfigured list that contains a ready made renderer with defaults that make sense for the most common use cases. It still retains most of the power available to the `List` component but reduces the complexity of one of the hardest things to grasp for most developers: rendering. @@ -1438,7 +1438,7 @@ hi.add(BorderLayout.CENTER, ml); .Basic usage of the MultiList & DefaultListModel] image::img/components-multilist.png[Basic usage of the MultiList and DefaultListModel,scaledwidth=20%] -`createListEntry` is relatively trivial: +`createListEntry` is trivial: [source,java] ---- @@ -1468,7 +1468,7 @@ image::img/graphics-urlimage-multilist.png[With cover images in place,scaledwidt TIP: Since the `MultiList` uses the `GenericListCellRenderer` internally you can use https://www.codenameone.com/javadoc/com/codename1/ui/URLImage.html[URLImage] to dynamically fetch the data. This is discussed in the graphics section of this guide. -===== Going Further With the ListModel +===== Going further with the ListModel Lets assume that http://www.georgerrmartin.com/[GRRM] was prolific and wrote 1 million books. The default list model won't make much sense in that case but you would still be able to render everything in a list model. @@ -1551,7 +1551,7 @@ MultiList ml = new MultiList(new GRMMModel()); image::img/components-millionbooks.png[It took ages to scroll this far... This goes to a million...,scaledwidth=20%] -==== List Cell Renderer +==== List cell renderer The Renderer is a simple interface with 2 methods: @@ -1581,7 +1581,7 @@ public Component getListFocusComponent(List list){ This will compile and work, but won't give you much, notice that you won't see the `List` selection move on the List, this is because the renderer returns a https://www.codenameone.com/javadoc/com/codename1/ui/Label.html[Label] with the same style regardless if it's selected or not. -Now Let you try to make it a bit more useful. +Now Let's try to make it a bit more useful. [source,java] ---- @@ -1600,7 +1600,7 @@ if (isSelected) { } ---- -In this renderer you set the `Label.setFocus(true)` if it's selected, calling to this method doesn't give the focus to the Label, it simply renders the label as selected. +In this renderer you set the `Label.setFocus(true)` if it's selected, calling to this method doesn't give the focus to the Label, it renders the label as selected. Then you invoke `Label.getAllStyles().setBgTransparency(100)` to give the selection semi transparency, and `0` for full transparency if not selected. @@ -1667,11 +1667,11 @@ class ContactsRenderer extends Container implements ListCellRenderer { } ---- -In this renderer you want to render a `Contact` object to the Screen, you build the `Component` in the constructor and in the getListCellRendererComponent you simply update the Labels' texts according to the `Contact` object. +In this renderer you want to render a `Contact` object to the Screen, you build the `Component` in the constructor and in the getListCellRendererComponent you update the Labels' texts according to the `Contact` object. Notice that in this renderer you return a focus `Label` with semi transparency, as mentioned before, the focus component can be modified within this method. -For example, I can modify the focus `Component` to have an icon. +For example, I can change the focus `Component` to have an icon. [source,java] ---- @@ -1684,7 +1684,7 @@ try { } ---- -==== Generic List Cell Renderer +==== Generic list cell renderer As part of the GUI builder work, you needed a way to customize rendering for a List, but the renderer/model approach seemed impossible to adapt to a GUI builder (it seems the Swing GUI builders had a similar issue). Your solution was to introduce the `GenericListCellRenderer`, which while introducing limitations and implementation requirements still manages to make life easier, both in the GUI builder and outside of it. @@ -1703,7 +1703,7 @@ For a model that contains a `Map` entry like this: A renderer will loop over the component hierarchy in the container, searching for components whose name matches Foo, X, Not, and Number, and assigning the appropriate value to them. -TIP: You can also use image objects as values, and they will be assigned to labels as expected. For example, you can't assign both an image and a text to a single label, since the key will be taken. That isn't a big problem, since two labels can be used easily in such a renderer. +TIP: You can also use image objects as values, and they will be assigned to labels as expected. For example, you can't assign both an image and a text to a single label, since the key will be taken. That isn't a big problem, since two labels can be used in such a renderer. To make matters even more attractive the renderer seamlessly supports list tickering when appropriate, and if a https://www.codenameone.com/javadoc/com/codename1/ui/CheckBox.html[CheckBox] appears within the renderer, it will toggle a boolean flag within the `Map` seamlessly. @@ -1715,9 +1715,9 @@ Naming a component within the renderer with $number will automatically set it as Styling the `GenericListCellRenderer` is slightly different, the renderer uses the `UIID` of the `Container` passed to the generic list cell renderer, and the background focus uses that same `UIID` with the word "Focus" appended to it. -Note that the generic list cell renderer will grant focus to the child components of the selected entry if they are focusable, thus changing the style of said entries. For example: a https://www.codenameone.com/javadoc/com/codename1/ui/Container.html[Container] might have a child `Label` that has one style when the parent container is unselected and another when it's selected (focused), this can be easily achieved by defining the label as focusable. Notice that the component will never receive direct focus, since it's still part of a renderer. +Note that the generic list cell renderer will grant focus to the child components of the selected entry if they are focusable, thus changing the style of said entries. For example: a https://www.codenameone.com/javadoc/com/codename1/ui/Container.html[Container] might have a child `Label` that has one style when the parent container is unselected and another when it's selected (focused), this can be achieved by defining the label as focusable. Notice that the component will never receive direct focus, since it's still part of a renderer. -Last but not least, the generic list cell renderer accepts two or four instances of a Container, rather than the obvious choice of accepting one instance. This allows the renderer to treat the selected entry differently, which is especially important to tickering, although it's also useful for the fisheye effect footnote:[Fisheye is an effect where the selection stays in place as the list moves around it]. Since it might not be practical to seamlessly clone the `Container` for the renderer's needs, Codename One expects the developer to provide two separate instances, they can be identical in all respects, but they must be separate instances for tickering to work. The renderer also allows for a fisheye effect, where the selected entry is actually different from the unselected entry in its structure, it also allows for a pinstripe effect, where odd/even rows have different styles (this is accomplished by providing 4 instances of the containers selected/unselected for odd/even). +Finally, the generic list cell renderer accepts two or four instances of a Container, rather than the obvious choice of accepting one instance. This allows the renderer to treat the selected entry differently, which is important to tickering, although it's also useful for the fisheye effect footnote:[Fisheye is an effect where the selection stays in place as the list moves around it]. Since it might not be practical to seamlessly clone the `Container` for the renderer's needs, Codename One expects the developer to provide two separate instances, they can be identical in all respects, but they must be separate instances for tickering to work. The renderer also allows for a fisheye effect, where the selected entry is actually different from the unselected entry in its structure, it also allows for a pinstripe effect, where odd/even rows have different styles (this is accomplished by providing 4 instances of the containers selected/unselected for odd/even). The best way to learn about the generic list cell renderer and the `Map` model is by playing with them in the old GUI builder. Notice they can be used in code without any dependency on the GUI builder and can be useful at that. @@ -1772,11 +1772,11 @@ private Object[] createGenericListCellRendererModelData() { .GenericListCellRenderer demo code image::img/components-generic-list-cell-renderer.png[GenericListCellRenderer demo code,scaledwidth=20%] -===== Custom UIID Of Entry in GenenricListCellRenderer/MultiList +===== Custom UIID of entry in GenenricListCellRenderer/MultiList With https://www.codenameone.com/javadoc/com/codename1/ui/list/MultiList.html[MultiList]/`GenenricListCellRenderer` one of the common issues is making a UI where a specific component within the list renderer has a different UIID style based on data. For example: this can be helpful to mark a label within the -list as red, for instance, in the case of a list of monetary transactions. +list as red, for instance, for a list of monetary transactions. This can be achieved with a custom renderer, but that's a pretty difficult task. + `GenericListCellRenderer` (`MultiList` uses `GenericListCellRenderer` internally) has another option. @@ -1805,7 +1805,7 @@ map.put("componentName_uiid", "blue"); Otherwise the component will stay red for the next entry (since the renderer acts like a rubber stamp). -===== Rendering Prototype +===== Rendering prototype Because of the rendering architecture of a `List` its pretty hard to calculate the right preferred size for such a component. The default behavior includes querying a few entries from the model then constructing their renderers to get a "sample" of the preferred size value. @@ -1829,8 +1829,8 @@ The https://www.codenameone.com/javadoc/com/codename1/ui/ComboBox.html[ComboBox] TIP: The `ComboBox` UI paradigm isn't as common on OS's such as iOS where there is no native equivalent to it. Recommend using either the https://www.codenameone.com/javadoc/com/codename1/ui/spinner/Picker.html[Picker] class or the https://www.codenameone.com/javadoc/com/codename1/ui/AutoCompleteTextField.html[AutoCompleteTextField]. -`ComboBox` is notoriously hard to style properly as it relies on a complex dynamic of popup renderer and instantly visible renderer. The `UIID` for the `ComboBox` is `ComboBox` -however if you set it to something else all the other `UIID's` will also change their prefix. For example: the `ComboBoxPopup` +`ComboBox` is notoriously hard to style as it relies on a complex dynamic of popup renderer and instantly visible renderer. The `UIID` for the `ComboBox` is `ComboBox` +but if you set it to something else all the other `UIID's` will also change their prefix. For example: the `ComboBoxPopup` `UIID` will become `MyNewUIIDPopup`. The combo box defines the following UIID's by default: @@ -1882,7 +1882,7 @@ image::img/slider.png[Slider,scaledwidth=25%] The interesting part about the slider is that it has two separate style `UIID’s`, `Slider` & `SliderFull`. The `Slider` `UIID` is always painted and `SliderFull` is rendered on top based on the amount the `Slider` should be filled. -`Slider` is highly customizable for example: a slider can be used to replicate a 5 star rating widget as such. Notice that this slider will work when its given its preferred size otherwise additional stars will appear. that's why you place it within a `FlowLayout`: +`Slider` is highly customizable for example: a slider can be used to replicate a 5 star rating widget as such. Notice that this slider will work when its given its preferred size otherwise more stars will appear. that's why you place it within a `FlowLayout`: [source,java] ---- @@ -1934,14 +1934,14 @@ private void showStarPickingForm() { .Star Slider set to 5 (its between 0 - 10) image::img/components-slider.png[Star Slider set to 5 (its between 0 - 10),scaledwidth=25%] -TIP: This slider goes all the way to 0 stars which is less common. You can use a `Label` to represent the first star and have the slider work between 0 - 8 values to provide 4 additional stars. +TIP: This slider goes all the way to 0 stars which is less common. You can use a `Label` to represent the first star and have the slider work between 0 - 8 values to provide 4 more stars. [[table-section]] === Table https://www.codenameone.com/javadoc/com/codename1/ui/table/Table.html[Table] is a composite component (but it isn't a <>), this means it's a subclass of https://www.codenameone.com/javadoc/com/codename1/ui/Container.html[Container]. it's effectively built from many components. -TIP: `Table` is heavily based on the https://www.codenameone.com/javadoc/com/codename1/ui/table/TableLayout.html[TableLayout] class. it's important to be familiar with that layout manager when working with `Table`. +TIP: `Table` is based on the https://www.codenameone.com/javadoc/com/codename1/ui/table/TableLayout.html[TableLayout] class. it's important to be familiar with that layout manager when working with `Table`. Here is a trivial sample of using the standard table component: @@ -2002,9 +2002,9 @@ hi.add(BorderLayout.CENTER, table); .Table with spanning & fixed widths to 33% image::img/components-table-with-spanning.png[Table with spanning and fixed widths to 33%,scaledwidth=20%] -In order to customize the table cell behavior you can derive the `Table` to create a "renderer like" widget, however unlike the list this component is "kept" and used as is. This means you can bind listeners to this component and work with it as you would with any other component in Codename One. +To customize the table cell behavior you can derive the `Table` to create a "renderer like" widget, but unlike the list this component is "kept" and used as is. This means you can bind listeners to this component and work with it as you would with any other component in Codename One. -So lets fix the example above to include far more capabilities: +lets fix the example above to include far more capabilities: [source,java] ---- Table table = new Table(model) { @@ -2098,9 +2098,9 @@ image::img/components-table-multiline-portrait.png[Multiline table cell in portr .Multiline table cell in landscape mode. Notice the cell row count adapts seamlessly image::img/components-table-multiline-landscape.png[Multiline table cell in landscape mode. Notice the cell row count adapts seamlessly,scaledwidth=20%] -==== Sorting Tables +==== Sorting tables -Sorting tables by clicking the titles is something that should generally work out of the box by using an API like `setSortSupported(true)`. +Sorting tables by clicking the titles is something that should work out of the box by using an API like `setSortSupported(true)`. [source,java] ---- @@ -2118,7 +2118,7 @@ hi.add(NORTH, new Button("Button")); hi.show(); ---- -Notice this works with numbers, Strings and might work with dates but you can generally support any object type by overriding the method `protected Comparator createColumnSortComparator(int column)` which should return a comparator for your custom object type in the column. +Notice this works with numbers, Strings and might work with dates but you can support any object type by overriding the method `protected Comparator createColumnSortComparator(int column)` which should return a comparator for your custom object type in the column. [[tree-section]] === Tree @@ -2126,7 +2126,7 @@ Notice this works with numbers, Strings and might work with dates but you can ge https://www.codenameone.com/javadoc/com/codename1/ui/tree/Tree.html[Tree] allows displaying hierarchical data such as folders and files in a collapsible/expandable UI. Like the <> it's a composite component (but it isn't a <>). Like the `Table` it works in consort with a model to construct its user interface on the fly but doesn't use a stateless renderer (as `List` does). -The data of the `Tree` arrives from a model model for example: this: +The data of the `Tree` arrives from a model for example: this: [source,java] ---- @@ -2275,7 +2275,7 @@ IMPORTANT: `ShareButton` behaves differently on the device... image::img/components-sharebutton-android.png[The share button running on the Android device and screenshot sent into twitter,scaledwidth=50%] -IMPORTANT: The `ShareButton` features some share service classes to allow plugging in additional share services. For example, this functionality is relevant to devices where native sharing isn't supported. So this code isn't used on iOS/Android... +IMPORTANT: The `ShareButton` features some share service classes to allow plugging in more share services. For example, this functionality is relevant to devices where native sharing isn't supported. this code isn't used on iOS/Android... === Tabs @@ -2301,7 +2301,7 @@ hi.add(BorderLayout.CENTER, t); .Simple usage of Tabs image::img/components-tabs.png[Simple usage of Tabs,scaledwidth=20%] -A common usage for `Tabs` is the the swipe to proceed effect which is common in iOS applications. In the code below you use https://www.codenameone.com/javadoc/com/codename1/ui/RadioButton.html[RadioButton] and https://www.codenameone.com/javadoc/com/codename1/ui/layouts/LayeredLayout.html[LayeredLayout] with hidden tabs to produce that effect: +A common usage for `Tabs` is the swipe to proceed effect which is common in iOS applications. In the code below you use https://www.codenameone.com/javadoc/com/codename1/ui/RadioButton.html[RadioButton] and https://www.codenameone.com/javadoc/com/codename1/ui/layouts/LayeredLayout.html[LayeredLayout] with hidden tabs to produce that effect: [source,java] ---- @@ -2447,8 +2447,8 @@ iv.setImageList(new DefaultListModel<>(red, green, blue, gray)); hi.add(BorderLayout.CENTER, iv); ---- -.An ImageViewer with many elements is indistinguishable from a single ImageViewer with the exception of swipe -image::img/components-imageviewer-multi.png[An ImageViewer with many elements is indistinguishable from a single ImageViewer with the exception of swipe,scaledwidth=20%] +.An ImageViewer with many elements is indistinguishable from a single ImageViewer except for swipe +image::img/components-imageviewer-multi.png[An ImageViewer with many elements is indistinguishable from a single ImageViewer except for swipe,scaledwidth=20%] Notice that you use a https://www.codenameone.com/javadoc/com/codename1/ui/list/ListModel.html[ListModel] to allow swiping between images. @@ -2564,7 +2564,7 @@ This fetches the images in the URL asynchronously and fires a data change event https://www.codenameone.com/javadoc/com/codename1/components/ScaleImageLabel.html[ScaleImageLabel] & https://www.codenameone.com/javadoc/com/codename1/components/ScaleImageButton.html[ScaleImageButton] allow you to position an image that will grow/shrink to fit available space. In that sense they differ from <> & <> which keeps the image at the same size. -TIP: The default UIID of `ScaleImageLabel` is "`Label`", however the default UIID of `ScaleImageButton` is "`ScaleImageButton`". The reasoning for the difference is that the "`Button`" UIID includes a border and a lot of legacy. +TIP: The default UIID of `ScaleImageLabel` is `Label`, but the default UIID of `ScaleImageButton` is `ScaleImageButton`. The reasoning for the difference is that the `Button` UIID includes a border and a lot of legacy. You can use `ScaleImageLabel`/`ScaleImageButton` interchangeably. The major difference between these components is the buttons ability to handle click events/focus. @@ -2597,7 +2597,7 @@ WARNING: When styling these components keep in mind that changing attributes suc === Toolbar The https://www.codenameone.com/javadoc/com/codename1/ui/Toolbar.html[Toolbar] API provides deep customization of the title bar area with more flexibility for example: placing a https://www.codenameone.com/javadoc/com/codename1/ui/TextField.html[TextField] -for search or buttons in arbitrary title area positions. The `Toolbar` API replicates some of the native functionality +for search or buttons in arbitrary title area positions. The `Toolbar` API replicates some native functionality available on Android/iOS and integrates with features such as the side menu to provide fine grained control over the title area behavior. The `Toolbar` needs to be installed into the `Form` in order for it to work. You can setup the Toolbar in one of these three ways: @@ -2640,11 +2640,11 @@ image::img/components-toolbar-sidemenu.png[The default sidemenu of the Toolbar,s .The overflow menu of the Toolbar image::img/components-toolbar-overflow-menu.png[The overflow menu of the Toolbar,scaledwidth=25%] -Normally you can set a title with a `String` but if you would want the component to be a text field or a multi +You can set a title with a `String` but if you would want the component to be a text field or a multi line label you can use `setTitleComponent(Component)` which allows you to install any component into the title area. -TIP: The code below demonstrates searching using custom code however the Toolbar also has built-in support for search covered in the next section +TIP: The code below demonstrates searching using custom code but the Toolbar also has built-in support for search covered in the next section The customization of the title area allows for some pretty powerful UI effects for example: the code below allows searching dynamically within a set of entries and uses some neat tricks: @@ -2707,10 +2707,10 @@ hi.show(); <2> You use the `DataChangeListener` to update the search results as you type them. <3> Hidden & Visible use the opposite flag values to say similar things (for example: when hidden is set to false you would want to set visible to true). + -Visible indicates whether a component can be seen. It will still occupy the physical space on the screen even when it isn't visible. Hidden will remove the space occupied by the component from the screen, but some code might still try to paint it. Normally, visible is redundant but you use it with hidden for good measure. +Visible indicates whether a component can be seen. It will still occupy the physical space on the screen even when it isn't visible. Hidden will remove the space occupied by the component from the screen, but some code might still try to paint it., visible is redundant but you use it with hidden for good measure. <4> The search button is totally unnecessary here. You can click the `TextField`! + -For example, that isn't intuitive to most users so you added the button to start editing. +For example, that isn't intuitive to most users so a button is added to start editing. .Search field within the toolbar image::img/components-toolbar-search.png[Search field within the toolbar,scaledwidth=20%] @@ -2718,7 +2718,7 @@ image::img/components-toolbar-search.png[Search field within the toolbar,scaledw .Search field after typing a couple of letters image::img/components-toolbar-search-ongoing.png[Search field after typing a couple of letters,scaledwidth=20%] -==== Search Mode +==== Search mode While you can implement search manually using the built-in search offers a simpler and more uniform UI. @@ -2805,7 +2805,7 @@ This places the component below the side menu bar. Notice that this component co [[title-animations-section]] -==== Title Animations +==== Title animations Modern UI's often animate the title upon scrolling to balance the highly functional smaller title advantage with the gorgeous large image based title. This is pretty easy to do with the Toolbar API thru the Title animation API. @@ -2847,7 +2847,7 @@ image::img/components-toolbar-animation-2.png[As you scroll down the image fades .As scrolling continues the title reaches standard size image::img/components-toolbar-animation-3.png[As scrolling continues the title reaches standard size,scaledwidth=20%] -Almost all of the code above creates the "look" of the application. The key piece of code above is this: +Most the code above creates the "look" of the application. The key piece of code above is this: [source,java] ---- @@ -2886,7 +2886,7 @@ image::img/components-browsercomponent.png[Browser Component showing the Codenam NOTE: The scrollbars appear in the simulator, device versions of the browser component act differently and support touch scrolling. -IMPORTANT: A `BrowserComponent` should be in the center of a BorderLayout. Otherwise its preferred size might be zero before the HTML finishes loading/layout in the native layer and layout might be incorrect as a result. +IMPORTANT: A `BrowserComponent` should be in the center of a BorderLayout. Otherwise its preferred size might be zero before the HTML finishes loading/layout in the native layer and layout might be wrong as a result. You can use `WebBrowser` and `BrowserComponent` interchangeably for most basic usage. For example, if you need access to JavaScript or native browser functionality then there is no use in going thru the `WebBrowser` abstraction. @@ -2900,7 +2900,7 @@ wb.setURL("jar:///Page.html"); IMPORTANT: On Android a native indicator might show up when the web page is loading. This can be disabled using the `Display.getInstance().setProperty("WebLoadingHidden", "true");` call. You need to invoke this once. -==== BrowserComponent Hierarchy +==== BrowserComponent hierarchy When Codename One packages applications into native apps it hides a lot of details to make the process simpler. One of the things hidden is the fact that you aren't dealing with a JAR anymore, so `getResource`/`getResourceAsStream` are problematic... Both of these API's support hierarchies and a concept of package @@ -2910,7 +2910,7 @@ Codename One has its own getResourceAsSteam in the https://www.codenameone.com/j TIP: that's why recommend that you place files inside res files. A resource file allows you to add arbitrary data files and you can have as many resource files as you need. -For web developers this isn't enough since hierarchies are used often to represent the various dependencies, this means that many links & references are relative. To work with such hierarchies place all of your resources in a hierarchy under the html package in the project source directory (`src/html`). The build server will `tar` the entire content of that package and add an `html.tar` file into the native package. This `tar` is seamlessly extracted on the device when you actually need the resources and with new application versions (not on every launch). So assuming the resources are under the html root package they can be displayed with code like this: +For web developers this isn't enough since hierarchies are used often to represent the various dependencies, this means that many links & references are relative. To work with such hierarchies place all your resources in a hierarchy under the html package in the project source directory (`src/html`). The build server will `tar` the entire content of that package and add an `html.tar` file into the native package. This `tar` is seamlessly extracted on the device when you actually need the resources and with new application versions (not on every launch). assuming the resources are under the html root package they can be displayed with code like this: [source,java] ---- @@ -2933,9 +2933,9 @@ At the core of the `BrowserComponent` you've the https://www.codenameone.com/jav You can bind a `BrowserNavigationCallback` by invoking `setBrowserNavigationCallback` on the `BrowserComponent`. At that point with every navigation within the browser the callback will get invoked. IMPORTANT: The `shouldNavigate` method from the `BrowserNavigationCallback` is invoked in a native thread and **NOT ON THE EDT**! + -it's crucial that this method returns immediately and that it won't do any changes on the UI. +it's crucial that this method returns and that it won't do any changes on the UI. -The `shouldNavigate` indicates to the native code whether navigation should proceed or not. For example: if a user clicks a specific link you might choose to do something in the Java code so you can return false and block the navigation. You can invoke https://www.codenameone.com/javadoc/com/codename1/ui/Display.html#callSerially-java.lang.Runnable-[callSerially] to do the actual task in the Java side. +The `shouldNavigate` indicates to the native code whether navigation should proceed or not. For example: if a user clicks a specific link you might choose to do something in the Java code so you can return false and block the navigation. You can invoke https://www.codenameone.com/javadoc/com/codename1/ui/Display.html#callSerially java.lang.Runnable-[callSerially] to do the actual task in the Java side. [source,java] ---- @@ -2978,26 +2978,26 @@ NOTE: The JavaScript Bridge is implemented on top of the `BrowserNavigationCallb TIP: The JavaScript bridge is sometimes confused with the JavaScript Port. The JavaScript bridge allows you to communicate with JavaScript from Java (and visa versa). The JavaScript port allows you to compile the Codename One application into a JavaScript application that runs in a standard web browser without code changes (think GWT without source changes and with thread support).+ You discuss the JavaScript port further later in the guide. -Codename One 4.0 introduced a new API for interacting with Javascript in Codename One. This API is part of the `BrowserComponent` class, and effectively replaces the https://www.codenameone.com/javadoc/com/codename1/javascript/package-summary.html[com.codename1.javascript package], which is now deprecated. +Codename One 4.0 introduced a new API for interacting with Javascript in Codename One. This API is part of the `BrowserComponent` class, and effectively replaces the https://www.codenameone.com/javadoc/com/codename1/JavaScript/package-summary.html[com.codename1.JavaScript package], which is now deprecated. -===== So what was wrong with the old API? +===== So what was wrong with the old API -The old API provided a synchronous wrapper around an inherently asynchronous process, and made extensive use of `invokeAndBlock()` underneath the covers. This resulted in a nice API with high-level abstractions that played nicely with a synchronous programming model, but it came with a price-tag in terms of performance, complexity, and predictability. Let’s take a simple example, getting a reference to the "window" object: +The old API provided a synchronous wrapper around an inherently asynchronous process, and made extensive use of `invokeAndBlock()` underneath the covers. This resulted in a nice API with high-level abstractions that played with a synchronous programming model, but it came with a price-tag in performance, complexity, and predictability. Let’s take a simple example, getting a reference to the "window" object: [source,java] ---- JSObject window = ctx.get("window"); ---- -This code looks harmless enough, but this is actually expensive. It issues a command to the `BrowserComponent`, and uses `invokeAndBlock()` to wait for the command to go through and send back a response. `invokeAndBlock()` is a magical tool that allows you to "block" without blocking the EDT, but it has its costs, and shouldn’t be overused. Most of the Codename One APIs that use `invokeAndBlock()` indicate this in their name. For example: `Component.animateLayoutAndWait()`. This provides you the expectation that this call could take some time, and helps to alert you to the underlying cost. +This code looks harmless enough, but this is actually expensive. It issues a command to the `BrowserComponent`, and uses `invokeAndBlock()` to wait for the command to go through and send back a response. `invokeAndBlock()` is a magical tool that allows you to "block" without blocking the EDT, but it has its costs, and shouldn’t be overused. Most of the Codename One APIs that use `invokeAndBlock()` show this in their name. For example: `Component.animateLayoutAndWait()`. This provides you the expectation that this call could take some time, and helps to alert you to the underlying cost. -The problem with the `ctx.get("window")` call is that it looks the same as a call to `Map.get(key)`. There’s no indication that this call is expensive and could take time. One call like this probably isn't a big deal, but it doesn't take long before you've dozens or even hundreds of calls like this littered throughout your codebase, and they can be hard to pick out. +The problem with the `ctx.get("window")` call is that it looks the same as a call to `Map.get(key)`. There’s no sign that this call is expensive and could take time. One call like this probably isn't a big deal, but it doesn't take long before you've dozens or even hundreds of calls like this littered throughout your codebase, and they can be hard to pick out. -===== The New API +===== The new API The new API fully embraces the asynchronous nature of Javascript. It uses callbacks instead of return values, and provides convenience wrappers with the appropriate "AndWait()" naming convention to allow for synchronous usage. Let’s look at a simple example: -NOTE: In all of the sample code below, you can assume that variables named `bc` represent an instance of https://www.codenameone.com/javadoc/com/codename1/ui/BrowserComponent.html[BrowserComponent]. +NOTE: In all the sample code below, you can assume that variables named `bc` represent an instance of https://www.codenameone.com/javadoc/com/codename1/ui/BrowserComponent.html[BrowserComponent]. [source,java] ---- @@ -3014,13 +3014,13 @@ This code should output "The result was 7" to the console. it's fully asynchrono public void execute(String js, SuccessCallback callback) ---- -The first parameter is a javascript expression. This javascript *MUST* call either `callback.onSuccess(result)` or `callback.onError(message, errCode)` at some point in order for your callback to be called. +The first parameter is a JavaScript expression. This JavaScript *MUST* call either `callback.onSuccess(result)` or `callback.onError(message, errCode)` at some point in order for your callback to be called. -The second parameter is your callback that's executed from the javascript side, when `callback.onSuccess(res)` is called. The callback takes a single parameter of type https://www.codenameone.com/javadoc/com/codename1/ui/BrowserComponent.JSRef.html[JSRef] which is a generic wrapper around a javascript variable. JSRef has accessors to retrieve the value as some of the primitive types. For example: `getBoolean()`, `getDouble()`, `getInt()`, `toString()`, and it provides some introspection through the `getType()` method. +The second parameter is your callback that's executed from the JavaScript side, when `callback.onSuccess(res)` is called. The callback takes a single parameter of type https://www.codenameone.com/javadoc/com/codename1/ui/BrowserComponent.JSRef.html[JSRef] which is a generic wrapper around a JavaScript variable. JSRef has accessors to retrieve the value as some primitive types. For example: `getBoolean()`, `getDouble()`, `getInt()`, `toString()`, and it provides some introspection through the `getType()` method. NOTE: it's worth noting that the callback method can take a single parameter. If you need to pass many parameters, you may consider including them in a single string which you parse in your callback. -===== Synchronous Wrappers +===== Synchronous wrappers As mentioned before, the new API also provides an `executeAndWait()` wrapper for `execute()` that will work synchronously. It, as its name suggests, uses `invokeAndBlock` under the hood so as not to block the EDT while it's waiting. @@ -3032,13 +3032,13 @@ JSRef res = bc.executeAndWait("callback.onSuccess(3+4)"); Log.p("The result was "+res.Int()); ---- -Prints "The result was 7". +Prints `The result was 7`. IMPORTANT: When using the `andWait()` variant, it's *extremely* important that your Javascript calls your callback method at some point - otherwise it will block *indefinitely*. You provide variants of executeAndWait() that include a timeout in case you want to hedge against this possibility. -===== Multi-use Callbacks +===== Multi-use callbacks -The callbacks you pass to `execute()` and `executeAndWait()` are single-use callbacks. You can’t, for example, store the `callback` variable on the javascript side for later use (for example: to respond to a button click event). If you need a "multi-use" callback, you should use the `addJSCallback()` method instead. Its usage looks identical to `execute()`, the difference is that the callback will life on after its first use. For example: Consider the following code: +The callbacks you pass to `execute()` and `executeAndWait()` are single-use callbacks. You can’t, for example, store the `callback` variable on the JavaScript side for later use (for example: to respond to a button click event). If you need a "multi-use" callback, you should use the `addJSCallback()` method instead. Its usage looks identical to `execute()`, the difference is that the callback will life on after its first use. For example: Consider the following code: [source,java] ---- @@ -3048,11 +3048,11 @@ bc.execute( ); ---- -The above example, assumes that jQuery is loaded in the webpage that you're interacting with, and you're adding a click handler to a button with ID "somebutton". The click handler calls your callback. +The above example, assumes that jQuery is loaded in the webpage that you're interacting with, and you're adding a click handler to a button with ID "somebutton." The click handler calls your callback. If you run this example, the first time the button is clicked, you’ll see "Button was clicked" printed to the console as expected. For example, the 2nd time, you’ll get an exception. This is because the callback passed to `execute()` is single-use. -You need to modify this code to use the `addJSCallback()` method as follows: +You need to change this code to use the `addJSCallback()` method as follows: [source,java] ---- @@ -3064,9 +3064,9 @@ bc.addJSCallback( Now it will work no matter how many times the button is clicked. -===== Passing Parameters to Javascript +===== Passing parameters to JavaScript -In many cases, the javascript expressions that you execute will include parameters from your java code. Properly escaping these parameters is tricky at worst, and annoying at best. For example: If you’re passing a string, you need to make sure that it escapes quotes and new lines properly or it will cause the javascript to have a syntax error. Luckily you provide variants of `execute()` and https://www.codenameone.com/javadoc/com/codename1/ui/BrowserComponent.html#addJSCallback-java.lang.String-com.codename1.util.SuccessCallback-[addJSCallback()] that allow you to pass your parameters and have them automatically escaped. +Often, the JavaScript expressions that you execute will include parameters from your Java code. Escaping these parameters is tricky at worst, and annoying at best. For example: If you’re passing a string, you need to make sure that it escapes quotes and new lines or it will cause the JavaScript to have a syntax error. You provide variants of `execute()` and https://www.codenameone.com/javadoc/com/codename1/ui/BrowserComponent.html#addJSCallback-java.lang.String-com.codename1.util.SuccessCallback-[addJSCallback()] that allow you to pass your parameters and have them automatically escaped. For example, suppose you want to pass a string with text to set in a textarea within the webpage. You can do something like: @@ -3081,13 +3081,13 @@ bc.execute( ); ---- -The gist is that you embed placeholders in the javascript expression that are replaced by the corresponding entry in an array of parameters. The `${0}` placeholder is replaced by the first item in the parameters array, the `${1}` placeholder is replaced by the 2nd, and so on. +The gist is that you embed placeholders in the JavaScript expression that are replaced by the corresponding entry in an array of parameters. The `${0}` placeholder is replaced by the first item in the parameters array, the `${1}` placeholder is replaced by the 2nd, etc.. -===== Proxy Objects +===== Proxy objects The new API also includes a https://www.codenameone.com/javadoc/com/codename1/ui/BrowserComponent.JSProxy.html[JSProxy] class that encapsulates a Javascript object simplify the getting and setting of properties on Javascript objects - and the calling of their methods. It provides essentially three core methods, along with many variants of each to allow for async or synchronous usages, parameters, and timeouts. -For example: You might want to create a proxy for the https://developer.mozilla.org/en-US/docs/Web/API/Window/location[window.location] object so that you can access its properties more easily from Java. +For example: You might want to create a proxy for the https://developer.mozilla.org/en-You/docs/Web/API/Window/location[window.location] object so that you can access its properties more from Java. [source,java] ---- @@ -3126,7 +3126,7 @@ location.call("replace", new Object[]{"http://www.google.com"}, ---- -===== Legacy JSObject Support +===== Legacy JSObject support This section describes the now deprecated `JSObject` approach. it's here for reference by developers working with older code. You suggest using the new API when starting a new project. @@ -3164,13 +3164,13 @@ NOTE: Notice that opening an alert in an embedded native browser might not work You use the `execute` method above to execute custom JavaScript code. You also have an `executeAndReturnString` method that allows you to receive a response value from the JavaScript side. -Coupled with `shouldNavigate` you can effectively do everything which is exactly what the JavaScript Bridge tries to do. +Coupled with `shouldNavigate` you can effectively do everything which is what the JavaScript Bridge tries to do. -===== The JavaScript Bridge +===== The JavaScript bridge -While it's possible to build everything on top of `execute` and `shouldNavigate`, both of these methods have their limits. that's why you introduced the javascript package, it allows you to communicate with JavaScript using intuitive code/syntax. +While it's possible to build everything on top of `execute` and `shouldNavigate`, both of these methods have their limits. that's why Codename One introduced the JavaScript package, it allows you to communicate with JavaScript using intuitive code/syntax. -The https://www.codenameone.com/javadoc/com/codename1/javascript/JavascriptContext.html[JavascriptContext] class lays the foundation by enabling you to call JavaScript code directly from Java. It provides automatic type conversion between Java and JavaScript types as follows: +The https://www.codenameone.com/javadoc/com/codename1/JavaScript/JavascriptContext.html[JavascriptContext] class lays the foundation by enabling you to call JavaScript code directly from Java. It provides automatic type conversion between Java and JavaScript types as follows: .Java to JavaScript [cols="2*",options="header"] @@ -3199,9 +3199,9 @@ The https://www.codenameone.com/javadoc/com/codename1/javascript/JavascriptConte | `undefined` | `null` |==== -NOTE: This conversion table is more verbose than necessary, since JavaScript functions and arrays are, in fact Objects themselves, so those rows are redundant. All JavaScript objects are converted to https://www.codenameone.com/javadoc/com/codename1/javascript/JSObject.html[JSObject]. +NOTE: This conversion table is more verbose than necessary, since JavaScript functions and arrays are, in fact Objects themselves, so those rows are redundant. All JavaScript objects are converted to https://www.codenameone.com/javadoc/com/codename1/JavaScript/JSObject.html[JSObject]. -You can access JavaScript variables easily from the context by using code like this: +You can access JavaScript variables from the context by using code like this: [source,java] ---- @@ -3237,7 +3237,7 @@ Notice that when you work with numeric values or anything related to the types m Double outerWidth = (Double)ctx.get("window.outerWidth"); ---- -You can also query the context for objects and modify their value for example: +You can also query the context for objects and change their value for example: [source,java] ---- @@ -3263,11 +3263,11 @@ bc.addWebEventListener("onLoad", (e) -> { This code effectively navigates to the Codename One home page by fetching the DOM's window object and setting its `location` property to https://www.codenameone.com/[https://www.codenameone.com/]. -==== Cordova/PhoneGap Integration +==== Cordova/PhoneGap integration PhoneGap was one of the first web app packager tools in the market. it's a tool that's effectively a browser component within a native wrapper coupled with native access API's. Cordova is the open source extension of this popular project. -Codename One supports embedding PhoneGap/Cordova applications directly into Codename One applications. This is relatively easy to do with the `BrowserComponent` and JavaScript integration. The main aspect that this integration requires is support for Cordova plugins & its JavaScript API's. +Codename One supports embedding PhoneGap/Cordova applications directly into Codename One applications. This is easy to do with the `BrowserComponent` and JavaScript integration. The main aspect that this integration requires is support for Cordova plugins & its JavaScript API's. The effort to integrate Cordova/PhoneGap support into Codename One is handled within an open source github project https://github.com/codenameone/CN1Cordova[here]. The chief benefits of picking Codename One rather than using Cordova directly are: @@ -3361,13 +3361,13 @@ String[] searchLocations(String text) { image::img/dynamic-autocomplete.png[Autocomplete Text Field with a webservice,scaledwidth=20%] -==== Using Images In AutoCompleteTextField +==== Using images in AutoCompleteTextField One question I got a few times is "How do you customize the results of the auto complete field"? -This sounds difficult to most people as you can work with Strings so how do you represent additional data or format the date correctly? +This sounds difficult to most people as you can work with Strings so how do you represent more data or format the date correctly? -The answer is actually pretty simple, you still need to work with Strings because auto-complete is first and foremost a text field. For example, that doesn't preclude your custom renderer from fetching data that might be placed in a different location and associated with the result. +The answer is actually pretty simple, you still need to work with Strings because auto-complete is fundamentally a text field. For example, that doesn't preclude your custom renderer from fetching data that might be placed in a different location and associated with the result. The following source code presents an auto-complete text field with images in the completion popup and two lines for every entry: @@ -3428,9 +3428,9 @@ current.add(ac); current.show(); ---- -<1> you've duplicate arrays that are partial for clarity. This is a separate list of data element but you can fetch the additional data from anywhere +<1> you've duplicate arrays that are partial for clarity. This is a separate list of data element but you can fetch the more data from anywhere <2> You create the renderer UI instantly in the fields with the helper methods for wrapping elements which is pretty cool & terse -<3> In a renderer it's important to always set the value especially if you don't have a value in place +<3> In a renderer it's important to always set the value if you don't have a value in place .Auto complete with images image::img/auto-complete-with-pictures.png[Auto complete with images,scaledwidth=20%] @@ -3548,7 +3548,7 @@ Button placement options are: === SwipeableContainer -The https://www.codenameone.com/javadoc/com/codename1/ui/SwipeableContainer.html[SwipeableContainer] allows you to place a component such as a https://www.codenameone.com/javadoc/com/codename1/components/MultiButton.html[MultiButton] on top of additional "options" +The https://www.codenameone.com/javadoc/com/codename1/ui/SwipeableContainer.html[SwipeableContainer] allows you to place a component such as a https://www.codenameone.com/javadoc/com/codename1/components/MultiButton.html[MultiButton] on top of more "options" that can be exposed by swiping the component to the side. This swipe gesture is commonly used in touch interfaces to expose features such as delete, edit etc. it's trivial to use this component by determining the components placed on top and bottom (the revealed component). @@ -3558,7 +3558,7 @@ This swipe gesture is commonly used in touch interfaces to expose features such SwipeableContainer swip = new SwipeableContainer(bottom, top); ---- -You can combine some of the demos above including the <> to rank GRRM's books in an interactive way: +You can combine some demos above including the <> to rank GRRM's books in an interactive way: [source,java] ---- @@ -3590,15 +3590,15 @@ image::img/components-swipablecontainer.png[SwipableContainer showing a common u https://www.codenameone.com/javadoc/com/codename1/ui/util/EmbeddedContainer.html[EmbeddedContainer] solves a problem that exists within the GUI builder and the class makes no sense outside of the context of the GUI builder. The necessity for `EmbeddedContainer` came about due to iPhone inspired designs that relied on tabs (iPhone style tabs at the bottom of the screen) where different features of the application are within a different tab. -This didn't mesh well with the GUI builder navigation logic and so you needed to rethink some of it. You wanted to reuse GUI as much as possible while still enjoying the advantage of navigation being completely managed for me. +This didn't mesh well with the GUI builder navigation logic and so you needed to rethink some of it. You wanted to reuse GUI as much as possible while still enjoying the advantage of navigation being managed for me. Android does this with Activities and the iPhone itself has a view controller, both approaches are problematic for Codename One. The problem is that you've what is effectively two incompatible hierarchies to mix and match. -The Component/Container hierarchy is powerful enough to represent such a UI but you needed a "marker" to indicate to the https://www.codenameone.com/javadoc/com/codename1/ui/util/UIBuilder.html[UIBuilder] where a "root" component exists so navigation occurs within the given "root". Here `EmbeddedContainer` comes into play, its a simple container that can contain another GUI from the GUI builder. Nothing else. So you can place it in any form of UI and effectively have the UI change appropriately and navigation would default to "sensible values". +The Component/Container hierarchy is powerful enough to represent such a UI but you needed a "marker" to show to the https://www.codenameone.com/javadoc/com/codename1/ui/util/UIBuilder.html[UIBuilder] where a "root" component exists so navigation occurs within the given "root." Here `EmbeddedContainer` comes into play, its a simple container that can contain another GUI from the GUI builder. Nothing else. you can place it in any form of UI and effectively have the UI change appropriately and navigation would default to "sensible values." Navigation replaces the content of the embedded container; it finds the embedded container based on the component that broadcast the event. If you want to navigate manually use the showContainer() method which accepts a component, you can give any component that's under the `EmbeddedContainer` you want to replace and Codename One will be smart enough to replace that component. -The nice part about using the `EmbeddedContainer` is that the resulting UI can be easily refactored to provide a more traditional form based UI without duplicating effort and can be easily adapted to a more tablet oriented UI (with a side bar) again without much effort. +The nice part about using the `EmbeddedContainer` is that the resulting UI can be refactored to provide a more traditional form based UI without duplicating effort and can be adapted to a more tablet oriented UI (with a side bar) again without much effort. === MapComponent @@ -3644,7 +3644,7 @@ map.show(); ---- The example below shows how to integrate the https://www.codenameone.com/javadoc/com/codename1/maps/MapComponent.html[MapComponent] with the Google https://www.codenameone.com/javadoc/com/codename1/location/Location.html[Location] API. -make sure to obtain your secret api key from the Google https://www.codenameone.com/javadoc/com/codename1/location/Location.html[Location] data API at: +make sure to get your secret api key from the Google https://www.codenameone.com/javadoc/com/codename1/location/Location.html[Location] data API at: https://developers.google.com/maps/documentation/places/ .MapComponent with Google Location API @@ -3741,10 +3741,10 @@ The `charts` package enables Codename One developers to add charts and visualiza having to include external libraries or embedding web views. You also wanted to harness the new features in the graphics pipeline to maximize performance. -==== Device Support +==== Device support -Since the charts package makes use of 2D transformations and shapes, it requires some of the graphics -features that aren't yet available on all platforms. Currently the following platforms are supported: +Since the charts package makes use of 2D transformations and shapes, it requires some graphics +features that aren't yet available on all platforms. The following platforms are supported: 1. Simulator 2. Android @@ -3757,10 +3757,10 @@ charts, pie charts and more. 2. **Pinch Zoom** - The https://www.codenameone.com/javadoc/com/codename1/charts/ChartComponent.html[ChartComponent] class includes optional pinch zoom support. 3. **Panning Support** - The https://www.codenameone.com/javadoc/com/codename1/charts/ChartComponent.html[ChartComponent] class includes optional support for panning. -==== Chart Types +==== Chart types The `com.codename1.charts` package includes models and renderers for many different types of charts. it's also -extensible so that you can add your own chart types if required. The following screen shots demonstrate a small +extensible so that you can add your own chart types if required. The following screen shots show a small sampling of the types of charts that can be created. .Line Charts @@ -3804,7 +3804,7 @@ NOTE: The above screenshots were taken from the https://github.com/codenameone/codenameone-demos/tree/master/ChartsDemo[ChartsDemo app]. You can start playing with this app by checking it out from your git repository. -==== How to Create A Chart +==== How to create a chart Adding a chart to your app involves four steps: @@ -3815,7 +3815,7 @@ want to display. the `com.codename1.charts.renderers` package. The renderer allows you to specify how the chart should look. For example: the colors, fonts, styles, to use. 3. **Create the Chart View**. Use one of the existing _view_ classes in the `com.codename1.charts.views` package. -4. **Create a https://www.codenameone.com/javadoc/com/codename1/charts/ChartComponent.html[ChartComponent]**. In order to add your chart to the UI, you need to wrap it in a https://www.codenameone.com/javadoc/com/codename1/charts/ChartComponent.html[ChartComponent] object. +4. **Create a https://www.codenameone.com/javadoc/com/codename1/charts/ChartComponent.html[ChartComponent]**. To add your chart to the UI, you need to wrap it in a https://www.codenameone.com/javadoc/com/codename1/charts/ChartComponent.html[ChartComponent] object. You can check out the https://github.com/codenameone/codenameone-demos/tree/master/ChartsDemo[ChartsDemo] app for specific examples, but here is a high-level view of some code that creates a Pie Chart. @@ -3893,7 +3893,7 @@ public Form createPieChartForm() { The https://www.codenameone.com/javadoc/com/codename1/ui/Calendar.html[Calendar] class allows you to display a traditional calendar picker and optionally highlight days in various ways. -NOTE: You normally recommend developers use the <> rather than use the calendar to pick a date. It looks better on the devices. +NOTE: You recommend developers use the <> rather than use the calendar to pick a date. It looks better on the devices. Simple usage of the `Calendar` class looks something like this: @@ -3967,11 +3967,11 @@ status.clear(); image::img/components-statusbar-multiline.png[ToastBar with a multiline message,scaledwidth=20%] -==== Actions In ToastBar +==== Actions in ToastBar Probably the best usage example for actions in toast is in the gmail style undo. If you aren't a gmail user then the gmail app essentially never prompts for confirmation! -It does whatever you ask and pops a "toast message" with an option to undo. So if you clicked by mistake you've 3-4 seconds to take that back. +It does whatever you ask and pops a "toast message" with an option to undo. if you clicked by mistake you've 3-4 seconds to take that back. This simple example shows you how you can undo any addition to the UI in a similar way to gmail: @@ -4049,7 +4049,7 @@ f.show(); .The Accordion Component image::img/components-accordion.png[The Accordion Component,scaledwidth=30%] -=== Floating Hint +=== Floating hint https://www.codenameone.com/javadoc/com/codename1/components/FloatingHint.html[FloatingHint] wraps a text component with a special container that can animate the hint label into a title label when the text component is edited or has content within it. @@ -4072,7 +4072,7 @@ image::img/components-floatinghint.png[The FloatingHint component with one compo The material design floating action button is a powerful tool for promoting an action within your application. https://www.codenameone.com/javadoc/com/codename1/components/FloatingActionButton.html[FloatingActionButton] is a round button that resides on top of the UI typically in the bottom right hand side. + -It has a drop shadow to distinguish it from the UI underneath and it can hide two or more additional actions under the surface. For example: you can create a simple single click button such as this: +It has a drop shadow to distinguish it from the UI underneath and it can hide two or more actions under the surface. For example: you can create a simple single click button such as this: [source,java] ---- @@ -4081,7 +4081,7 @@ fab.addActionListener(e -> ToastBar.showErrorMessage("Not implemented yet...")); fab.bindFabToContainer(form.getContentPane()); ---- -Which will place a `+` sign button that will perform the action. Alternatively you can create a nested action +Which will place a `+` sign button that will perform the action. Or you can create a nested action where a click on the button will produce a submenu for users to pick from for example: [source,java] @@ -4095,11 +4095,11 @@ fab.bindFabToContainer(form.getContentPane()); .FloatingActionButton with submenu expanded image::img/floating-action.png[FloatingActionButton with submenu expanded,scaledwidth=20%] -Those familiar with this widget know that there are many nuances to this UI that you might implement/expose in the future. At the moment you chose to keep the API simple and minimal for the common use cases and refine it based on feedback. +Those familiar with this widget know that there are many nuances to this UI that may be implemented/exposed in the future. The current API is intentionally simple and minimal for the common use cases, with the plan to refine it based on feedback. -==== Using Floating Button as a Badge +==== Using floating Button as a badge -Floating buttons can also be used to badge an arbitrary component in the style popularized by iOS/Mac OS. A badge appears at the top right corner and includes special numeric details such as "unread count".. +Floating buttons can also be used to badge an arbitrary component in the style popularized by iOS/Mac OS. A badge appears at the top right corner and includes special numeric details such as `unread count.`. The code below adds a simple badge to an icon button: @@ -4157,15 +4157,15 @@ private Container encloseInMaximizableGrid(Component cmp1, Component cmp2) { .Split Pane in the Kitchen Sink Demo image::img/splitpane.png[Split Pane in the Kitchen Sink Demo,scaledwidth=30%] -This is mostly self-explanatory but "mostly". you've 5 arguments the first 3 make sense: +This is self-explanatory but "mostly." you've 5 arguments the first 3 make sense: - Split orientation - Components to split The last 3 arguments seem weird but they also make sense once you understand them, they are: -- The minimum position of the split - 1/4 of available space +- The least position of the split - 1/4 of available space - The default position of the split - middle of the screen -- The maximum position of the split - 3/4 of available space +- The most position of the split - 3/4 of available space The units don't have to be percentages they can be mm (millimeters) or px (pixels). diff --git a/docs/developer-guide/Theme-Basics.asciidoc b/docs/developer-guide/Theme-Basics.asciidoc index d6e20c85ae..b52d061230 100644 --- a/docs/developer-guide/Theme-Basics.asciidoc +++ b/docs/developer-guide/Theme-Basics.asciidoc @@ -1,9 +1,9 @@ -== Theme Basics +== Theme basics -This chapter covers the creation of a simple hello-world style theme and its visual customization. It uses the Codename One Designer tool to demonstrate basic concepts in theme creation such as 9-piece borders, selectors, and style types. You should review this even if you end up using CSS. +This chapter covers the creation of a simple hello-world style theme and its visual customization. It uses the Codename One Designer tool to show basic concepts in theme creation such as 9-piece borders, selectors, and style types. You should review this even if you end up using CSS. [[theme-basics-section,Codename One Theme Basics]] -=== Understanding Codename One Themes +=== Understanding Codename One themes Codename One themes are pluggable CSS-like elements that let developers determine or switch the look of the application at runtime. A theme can be installed through the https://www.codenameone.com/javadoc/com/codename1/ui/plaf/UIManager.html[UIManager class], and themes can be layered one on top of another. A theme can also be generated from CSS, and this guide covers that and the supported CSS syntax later. @@ -17,7 +17,7 @@ classes to create the appearance of the application. Codename One themes have some built-in defaults. For example, there are borders for buttons and padding, margin, and opacity for various components. These are "`common sense`" defaults that you can override within the theme. -Codename One themes are effectively a set of UIIDs mapped to a https://www.codenameone.com/javadoc/com/codename1/ui/plaf/Style.html[Style] object. Codename One applications always have a theme. You can modify it to suit your needs, and you can add many themes within the main resource file. +Codename One themes are effectively a set of UIIDs mapped to a https://www.codenameone.com/javadoc/com/codename1/ui/plaf/Style.html[Style] object. Codename One applications always have a theme. You can change it to suit your needs, and you can add many themes within the main resource file. You can also add many resource files to a project and work with them. In code, initialize a theme like this in your main class: @@ -55,7 +55,7 @@ When you select the theme you will see the theme default view. .Theme default view image::img/theme-default-view.png[Theme default view] -There are many interesting things to notice here the preview section allows you to instantly see the changes you make to the theme data. +many interesting things to notice here the preview section allows you to instantly see the changes you make to the theme data. .Theme preview section image::img/theme-preview-section.png[Theme preview section,scaledwidth=50%] @@ -81,16 +81,16 @@ TIP: You can use the Component Inspector tool in the simulator to locate a compo .When pressing the Add/Edit entry you can edit a specific style entry UIID image::img/theme-add-edit-entry.png[When pressing the Add/Edit entry you can edit a specific style entry UIID,scaledwidth=50%] -When you add/edit an entry an important piece of the puzzle is the #Derive# check box that appears next to all of the UIID entries. All styles derive from the base style and usually from the native theme defaults, so when this flag is checked the defaults will be used. +When you add/edit an entry an important piece of the puzzle is the #Derive# check box that appears next to all the UIID entries. All styles derive from the base style and from the native theme defaults, so when this flag is checked the defaults will be used. When you uncheck that checkbox the fields below it become editable and you can override the default behavior. To restore the default recheck that flag. NOTE: A common oddity for developers is that when they press #Add# and don't derive any entry nothing is actually added. The entries in the theme are essentially key/value pairs so when you don't add anything there are no keys so the entry doesn't show up -=== Customizing The Title +=== Customizing the title -The title is a great target for customization since it includes a few interesting "complexities". +The title is a great target for customization since it includes a few interesting "complexities." The `Title` is surrounded by a `TitleArea` container that encloses it, above the title you will also see the `StatusBar` UIID that prevents the status details from drawing on top of the title text. @@ -99,9 +99,9 @@ image::img/theme-title-area-UIIDs.png[Title Area UIID's,scaledwidth=50%] TIP: The `StatusBar` UIID is a special case that's there on iOS. In iOS the application needs to render the section under the status bar (which isn't the case for other OS's) and the `StatusBar` UIID was added so developers can ignore that behavior. -==== Background Priorities and Types +==== Background priorities and types -A slightly confusing aspects of styles in Codename One is the priorities of backgrounds. When you define a specific type of background it will override prior definitions, this even applies to inheritance. +A slightly confusing aspects of styles in Codename One is the priorities of backgrounds. When you define a specific kind of background it will override prior definitions, this even applies to inheritance. For example: if the theme defined a border for the `Button` UIID (a common case) if you will try to define the background image or the background color of `Button` those will be ignored! @@ -116,11 +116,11 @@ The order for UIID settings for background is as follows: 3. Background Color/Transparency - If transparency is larger than 0 then this takes effect. -==== The Background Behavior and Image +==== The background behavior and Image -Lets start in the first page of the style entry, you will customize the background behavior for the `Title` UIID and demonstrate/explain some of the behaviors. +Lets start in the first page of the style entry, you will customize the background behavior for the `Title` UIID and show/explain some behaviors. -The pictures below demonstrate the different types of background image behaviors. +The pictures below show the different types of background image behaviors. .IMAGE_SCALED scales the image without preserving aspect ratio to fit the exact size of the component image::img/theme-background-image-scaled.png[IMAGE_SCALED scales the image without preserving aspect ratio to fit the exact size of the component] @@ -129,7 +129,7 @@ image::img/theme-background-image-scaled.png[IMAGE_SCALED scales the image witho image::img/theme-background-image-scaled-fill.png[IMAGE_SCALED_FILL scales the image while preserving aspect ratio so it fills the entire space of the component] TIP: Aspect ratio is the ratio between the width and the height of the image. For example: if the image is `100x50` pixels and you want the width to be 200 pixels preserving the aspect ratio will require the height to also double to `200x100`. + -You highly recommend preserving the aspect ratio to keep images more "natural". +You highly recommend preserving the aspect ratio to keep images more "natural." .IMAGE_SCALED_FIT scales the image while preserving aspect ratio so it fits within the component image::img/theme-background-image-scaled-fit.png[IMAGE_SCALED_FIT scales the image while preserving aspect ratio so it fits within the component] @@ -182,16 +182,16 @@ image::img/theme-background-image-aligned-bottom-right.png[IMAGE_ALIGNED_BOTTOM_ .IMAGE_ALIGNED_CENTER places the image in the middle of the component image::img/theme-background-image-aligned-center.png[IMAGE_ALIGNED_CENTER places the image in the middle of the component] -==== The Color Settings +==== The color settings The color settings are much simpler than the background behavior. As explained <> the priority for color is at the bottom so if you've a border, image or gradient defined the background color settings will be ignored. .Add theme entry color settings image::img/theme-entry-color.png[Add theme entry color settings] -There are three color settings: +three color settings: -- Foreground color is the RRGGBB color that sets the style foreground color normally used to draw the text of the component. You can use the color picker button on the side to pick a color +- Foreground color is the RRGGBB color that sets the style foreground color used to draw the text of the component. You can use the color picker button on the side to pick a color - Background same as foreground determines the background color of the component @@ -212,7 +212,7 @@ WARNING: Aligning text components to anything other than the default alignment m NOTE: Bidi/RtL layout reverses the alignment value so left becomes right and visa versa -==== Padding and Margin +==== Padding and margin Padding and margin are concepts derived from the CSS box model. They are slightly different in Codename One, where the border spacing is part of the padding, but other than that they are pretty similar: @@ -226,7 +226,7 @@ The theme allows you to customize the padding/margin, and specify them for all 4 .Padding tab image::img/theme-entry-padding.png[Padding tab] -TIP: We recommend using millimeters for all spacing to make it look good for all device densities. Percentages make sense in extreme cases. +TIP: You recommend using millimeters for all spacing to make it look good for all device densities. Percentages make sense in extreme cases. ==== Borders @@ -237,7 +237,7 @@ image::img/theme-entry-border-settings.png[Border entry in the theme] ==== 9-Piece Image Border -A common border type is the 9-piece image border. To simplify creating this type of border, there is a special #Image Border Wizard#. +A common border type is the 9-piece image border. To simplify creating this kind of border, there is a special #Image Border Wizard#. A 9-piece image border is a common convention in UI theming that divides a border into 9 pieces: 4 representing the corners, 4 representing the sides, and 1 representing the middle. @@ -254,18 +254,18 @@ image::img/theme-entry-9-piece-wizard-stage1.png[Stage 1: create or pick an imag TIP: Use an image that's designed for a high DPI device -For your convenience you can create a rudimentary image with the create image stage but for a professional looking application you would usually want to use a design by a professional designer. +For your convenience you can create a rudimentary image with the create image stage but for a professional looking application you would want to use a design by a professional designer. .Stage 2: Cutting the image and adapting it to the DPI's image::img/theme-entry-9-piece-wizard-stage2.png[Stage 2: Cutting the image and adapting it to the DPI's] The second stage is probably the hardest and most important one in this wizard! -You can change the values of the top/bottom/left/right spinners to move the position of the guide lines that indicate the various 9 pieces. The image shows the correct cut for this image type with special attention to the following: +You can change the values of the top/bottom/left/right spinners to move the position of the guide lines that show the various 9 pieces. The image shows the correct cut for this image type with special attention to the following: - The left/right position is high enough to fit in the rounded corners in their entirety. Notice that you didn't leave 1 pixel as that performs badly, you want to leave as much space as possible! -- The top and bottom lines have exactly one pixel between them. This is to avoid breaking the gradient. For example: if you set the lines further apart you will end up with this: +- The top and bottom lines have one pixel between them. This is to avoid breaking the gradient. For example: if you set the lines further apart you will end up with this: .This is why it's important to keep the lines close when a gradient is involved, notice the tiling effect... image::img/theme-entry-9-piece-wizard-stage2-bad-border.png[This is why it's important to keep the lines close when a gradient is involved, notice the tiling effect...,scaledwidth=30%] @@ -273,29 +273,31 @@ image::img/theme-entry-9-piece-wizard-stage2-bad-border.png[This is why it's imp .When the lines are close together the gradient effect grows more effectively image::img/theme-entry-9-piece-wizard-stage2-better-border.png[When the lines are close together the gradient effect grows more effectively,scaledwidth=30%] -- The elements on the right hand side include the #Generate Multi Image# options. Here you can indicate the density of the source image you're using (for example: if its for iPhone 5 class device pick High). You can then select in the checkboxes below the densities that should be generated automatically for you. This allows fine detail on the border to be maintained in the various high/low resolution devices. +- The elements on the right hand side include the #Generate Multi Image# options. Here you can show the density of the source image you're using (for example: if its for iPhone 5 class device pick High). You can then select in the checkboxes below the densities that should be generated automatically for you. This allows fine detail on the border to be maintained in the various high/low resolution devices. TIP: You go into a lot of details about multi images in the advanced theming section. .Stage 3: Styles to which the border is applied image::img/theme-entry-9-piece-wizard-stage3.png[Stage 3: Styles to which the border is applied] -The last page indicates the styles to which the wizard will apply the border. Under normal usage you don't need to touch this as its properly filled out. +The last page indicates the styles to which the wizard will apply the border. Under normal usage you don't need to touch this as its filled out. You can define the same border for many UIIDs from here though. +// vale-skip: write-good.TooWordy — 'Minimum Size' is a precise dimensional constraint label. .Border Minimum Size ***** A common oddity when using the image borders is the fact that even when padding is removed the component might take a larger size than the height of the text within it. +// vale-skip: write-good.TooWordy — 'minimum height/width' refers to a numeric lower bound, not a comparison. The reason for this is the border. Because of the way borders are implemented they can't be drawn to be smaller than the sum of their corners. For example: the minimum height of a border would be the height of the bottom corner + the height of the top corner. The minimum width would be the width of the left + right corners. -This is coded into the common preferred size methods in Codename One and components generally don't shrink below the size of the image border even if padding is 0. +This is coded into the common preferred size methods in Codename One and components don't shrink below the size of the image border even if padding is 0. ***** -===== Customizing The 9-Piece Border +===== Customizing the 9-Piece Border -Normally you can use the 9-piece border wizard but you can also customize the border by pressing the "..." button on the border section in the theme. +You can use the 9-piece border wizard but you can also customize the border by pressing the "..." button on the border section in the theme. .Press this to customize borders image::img/theme-entry-border-three-dot-button.png[Press this to customize borders] @@ -312,6 +314,7 @@ NOTE: Notice that the other elements in the UI are disabled when the image borde .3 Image Mode **** +// vale-skip: write-good.Weasel — 'rarely used' is the natural phrase here; deleting 'rarely' would lose the meaning. The 9-piece border has a (rarely used) special case: 3 image mode. In this mode a developer can specify the top left corner, the top image and the center image to produce a 9 piece border. The corner and top piece are then rotated dynamically to produce a standard 9-piece border on the device. This is useful for reducing application code size but isn't used often as it requires a more symetric UI. @@ -321,7 +324,7 @@ NOTE: don't confuse the 3-image mode for the 9-piece border with the horizontal/ ==== Horizontal/Vertical Image Border -The 9-piece border is the workhorse of borders in Codename One, however there are some edge cases of UI elements that should grow on one axis and not on another. A perfect example of this is the iOS 6 style back button. If you tried to cut it into a 9-piece border the arrow effect would be broken. +The 9-piece border is the workhorse of borders in Codename One, but there are some edge cases of UI elements that should grow on one axis and not on another. A perfect example of this is the iOS 6 style back button. If you tried to cut it into a 9-piece border the arrow effect would be broken. .Horizontal image border is commonly used for UI's that can't grow vertically for example: the iOS 6 style back button image::img/theme-entry-border-edit-horizontal-image.png[Horizontal image border is commonly used for UI's that can't grow vertically for example: the iOS style back button] @@ -334,20 +337,20 @@ TIP: In RTL/Bidi footnote:[Languages that are written from right to left such as Empty borders enforce the removal of a border. This is important if you would like to block a base style from having a border. -For example: Buttons have borders by default. If you would like to create a https://www.codenameone.com/javadoc/com/codename1/ui/Button.html[Button] that's strictly of solid color you could define the border to be empty and then use the solid color as you see fit. +For example: Buttons have borders by default. If you would like to create a https://www.codenameone.com/javadoc/com/codename1/ui/Button.html[Button] that's of solid color you could define the border to be empty and then use the solid color as you see fit. IMPORTANT: There is a null border which is often confused with an empty border. You should use empty border and not null border ==== Round Border -Circles and completely round border sides are problematic for multi-resolutions. You need to draw them dynamically and can't use image borders which can't be tiled/cut to fit round designs (due to physical constraints of the round shape). +Circles and round border sides are problematic for multi-resolutions. You need to draw them dynamically and can't use image borders which can't be tiled/cut to fit round designs (due to physical constraints of the round shape). You designed the `RoundBorder` to enable two distinct types of borders: - Circular borders - for example: Android floating action - Rectangles with round (not rounded) sides -**Round** Border is a bit confusing since you already support a **rounded** border type. The rounded border type is a rectangle with rounded corners whereas the round border has completely round sides or appears as a circle. +**Round** Border is a bit confusing since you already support a **rounded** border type. The rounded border type is a rectangle with rounded corners whereas the round border has round sides or appears as a circle. To make matters worse the round border has a ridiculous number of features/configurations that would have made the already cluttered UI darn near impossible to navigate. To simplify this you split the UI into 3 tabs for standard borders, image borders and round border. @@ -355,7 +358,7 @@ To make matters worse the round border has a ridiculous number of features/confi .Round Border image::img/round-border-theme.png[Round Border,scaledwidth=50%] -==== Rounded Rectangle Border +==== Rounded rectangle Border The `RoundRectBorder` was developed based on the `RoundBorder` and has similar features. It produces a rounded rectangle UI. @@ -367,9 +370,9 @@ it's a pretty simple border type akin to the `RoundBorder`. image::img/rounded-rectangle-border.png[Rounded Rectangle Border,scaledwidth=50%] -==== Bevel/Etched Borders +==== Bevel/Etched borders -You generally recommend avoiding bevel/etched border types as they aren't as efficient and look a bit dated in todays applications. You cover them here mostly for completeness. +You recommend avoiding bevel/etched border types as they aren't as efficient and look a bit dated in todays applications. You cover them here for completeness. .Bevel border image::img/theme-entry-border-edit-bevel.png[Bevel border] @@ -388,7 +391,7 @@ For example: Lets say you created a component that's supposed to look like a tit cmp.setUIID("Title"); ---- -But title might sometimes be aligned to the left (based on theme) and you always want your component to be center aligned. For example, you don't want that to affect the actual titles in the app... +However, title might sometimes be aligned to the left (based on theme) and you always want your component to be center aligned. For example, you don't want that to affect the actual titles in the app... To solve this you can define a `MyTitle` UIID and derive the `Title` UIID. Then customize that one attribute. @@ -399,22 +402,22 @@ image::img/theme-entry-derive.png[Derive title] **** Style inheritance is a problematic topic in every tool that supports such behavior. Codename One styles start from a global default then have a system default applied and on top of that have the native OS default applied to them. -At that point a developer can define the style after all of the user settings are in place. Normally this works reasonably well, but there are some edge cases where inheriting a style can fail. +At that point a developer can define the style after all the user settings are in place. This works reasonably well, but there are some edge cases where inheriting a style can fail. When you override an existing style such as `Button` and choose to derive from `Button` in a different selection mode or even a different component altogether such as `Label` you might trigger a recursion effect where a theme setting in the base theme depends on something in a base triggering an infinite loop. -To avoid this always inherit only from UIID's you defined e.g. `MyButton`. +To avoid this always inherit only from UIID's you defined for example, `MyButton`. **** ==== Fonts -Codename One currently supports 3 font types: +Codename One supports 3 font types: -- *System fonts* -- these are simplistic built-in fonts. They work on all platforms and come in one of 3 sizes. For example, they are ubiquitous and work in every platform in all languages. +- *System fonts:* these are simplistic built-in fonts. They work on all platforms and come in one of 3 sizes. For example, they are ubiquitous and work in every platform in all languages. -- *TTF files* -- you can place a TTF file in the src directory of the project and it will appear in the #True Type# combo box. +- *TTF files:* you can place a TTF file in the src directory of the project and it will appear in the #True Type# combo box. -- *Native fonts* -- these aren't supported on all platforms but generally they allow you to use a set of platform native good looking fonts. For example: on Android the devices Roboto font will be used and on iOS San Francisco or Helvetica Neue will be used. *This is the recommended font type you suggest for most use cases!* +- *Native fonts:* these aren't supported on all platforms but they allow you to use a set of platform native good looking fonts. For example: on Android the devices Roboto font will be used and on iOS San Francisco or Helvetica Neue will be used. *This is the recommended font type for most use cases.* WARNING: If you use a TTF file **MAKE SURE** not to delete the file when there **MIGHT** be a reference to it. This can cause hard to track down issues! @@ -448,9 +451,9 @@ if(Font.isTrueTypeFileSupported()) { Notice that, in code, pixel sizes are supported, so it’s up to you to decide how to convert that. Recommend using millimeters with the `convertToPixels` method. You also need to derive the font with the proper size, unless you want a 0 sized font which isn't useful. -The font name is the difficult bit, iOS requires the name of the font in order to load the font. This font name doesn't always correlate to the file name making this task rather "tricky". The actual font name is sometimes viewable within a font viewer. It isn't always intuitive, so be sure to test that on the device to make sure you got it right. +The font name is the difficult bit, iOS requires the name of the font to load the font. This font name doesn't always correlate to the file name making this task rather "tricky." The actual font name is sometimes viewable within a font viewer. It isn't always intuitive, so be sure to test that on the device to make sure you got it right. -IMPORTANT: Due to licensing restrictions Codename One doesn't bundle Apple's iOS fonts. In the simulator with an iOS skin you try to use installed San Francisco/SF Pro (or Helvetica Neue) fonts when available on your machine; otherwise you fall back to bundled Roboto. You can obtain Apple's font downloads and terms at https://developer.apple.com/fonts/ +IMPORTANT: Due to licensing restrictions Codename One doesn't bundle Apple's iOS fonts. In the simulator with an iOS skin you try to use installed San Francisco/SF Pro (or Helvetica Neue) fonts when available on your machine; otherwise you fall back to bundled Roboto. You can get Apple's font downloads and terms at https://developer.apple.com/fonts/ The code below demonstrates all the major fonts available in Codename One with the handlee ttf file posing as a standin for arbitrary TTF: @@ -557,7 +560,7 @@ image::img/theme-font-catalog.png[The fonts running on the ipad simulator on a M image::img/theme-font-catalog-opo.png[The same demo running on a OnePlus One device with Android 5.1,scaledwidth=60%] -===== Font Effects +===== Font effects You can define an effect to be applied to a specific font, specifically: @@ -568,4 +571,4 @@ You can define an effect to be applied to a specific font, specifically: The "3d" effects effectively draw the text twice, with a slight offset and two different colors to create a "3d" feel. -All of the effects are relatively simple and performant. +All the effects are simple and performant. diff --git a/docs/developer-guide/Video-Capture-Constraints.asciidoc b/docs/developer-guide/Video-Capture-Constraints.asciidoc index 3e812eecd7..6808db55e4 100644 --- a/docs/developer-guide/Video-Capture-Constraints.asciidoc +++ b/docs/developer-guide/Video-Capture-Constraints.asciidoc @@ -1,17 +1,18 @@ -== Video Capture Constraints API +== Video capture constraints API The new video capture constraints API allows you to specify "constraints" when capturing videos. Constraints include: 1. Video Quality (High or Low) +// vale-skip: write-good.TooWordy — 'Maximum duration' is a precise upper-bound label. 2. Maximum duration -3. Maximum file size -4. Video Resolution (i.e. width and height). +3. Most file size +4. Video Resolution (that is, width and height). -Support for these constraints vary by platform and device, but the API allows you to check if your constraints are supported at runtime. Essentially, you set your "preferred" constraints, and the API will give you its best attempt at meeting those constraints. This is similar to setting a visual Component's preferred width and height. The layout manager takes these preferred dimensions under advisement, but ultimately sets the size on its own. +Support for these constraints vary by platform and device, but the API allows you to check if your constraints are supported at runtime. Essentially, you set your "preferred" constraints, and the API will give you its best try at meeting those constraints. This is like setting a visual Component's preferred width and height. The layout manager takes these preferred dimensions under advisement, but sets the size on its own. -=== Example 1: Capturing a Low-Quality 5-Second Clip +=== Example 1: capturing a Low-Quality 5-Second clip -Suppose we want to allow the user to capture a short (5 second) clip, in a low resolution, appropriate for sharing on a social media platform. We create our `VideoCaptureConstraints` object as follows: +Suppose you want to allow the user to capture a short (5 second) clip, in a low resolution, appropriate for sharing on a social media platform. You create your `VideoCaptureConstraints` object as follows: [source,java] ---- @@ -20,7 +21,7 @@ VideoCaptureConstraints cnst = new VideoCaptureConstraint() .preferredMaxLength(5); ---- -This constraint can then be passed to `Capture.captureVideo()` to obtain the captured file. +This constraint can then be passed to `Capture.captureVideo()` to get the captured file. [source,java] ---- @@ -29,11 +30,9 @@ String videoPath = Capture.captureVideo(cnst); === Not all platforms support all constraints -So how do we know if our constraints will be obeyed? If the platform doesn't support the max length, constraint, we may want to do something different. You can find out if a constraint is supported by simply asking out constraint object. +how do you know if your constraints will be obeyed? If the platform doesn't support the max length, constraint, you may want to do something different. You can find out if a constraint is supported by asking out constraint object. -E.g. - -[source,java] +For example, [source,java] ---- if (cnst.isMaxLengthSupported()) { // The max length constraint that we specified is supported on this platform. @@ -60,9 +59,9 @@ You can probe a constraint to see whether the entire constraint is supported (i. * `isMaxFileSizeSupported()` - True if the max file size setting is supported. * `isSizeSupported()` - True if the specified preferred width and height constraints are supported. -=== Example 2: Specifying Explicit Width and Height +=== Example 2: specifying explicit width and height -Suppose we want to capture a video with resolution 320x240. We would begin with this constraint: +Suppose you want to capture a video with resolution 320×240. You would begin with this constraint: [source,java] ---- @@ -71,15 +70,15 @@ VideoCaptureConstraints cnst = new VideoCaptureConstraints() .preferredHeight(240); ---- -Explicit width and height constraints currently aren't well supported across platforms. Android doesn't support them at all. iOS supports 3 specific sizes. Javascript supports it when running on a desktop browser or on Android - but not on iOS. Etc.. +Explicit width and height constraints aren't well supported across platforms. Android doesn't support them at all. iOS supports 3 specific sizes. Javascript supports it when running on a desktop browser or on Android - but not on iOS. Etc.. -So let's find out if this constraint will be obeyed. +let's find out if this constraint will be obeyed. [source,java] ---- if (cnst.isSizeSupported()) { // Yay! This platform supports our constraint, so the captured video will - // be exactly 320x240. + // be exactly 320×240. } else { // Not supported... let's see if the platform will at least try to accommodate us int effectiveWidth = cnst.getWidth(); @@ -90,17 +89,17 @@ if (cnst.isSizeSupported()) { // In many cases it will try to at least set the quality approximate if (quality != 0) { // The platform set the quality for us to try to comply. - // Since 320x240 is pretty small, the quality would probably + // Since 320×240 is pretty small, the quality would probably // be set to QUALITY_LOW } } else { - // The platform couldn't capture at 320x240, but it has provided an + // The platform couldn't capture at 320×240, but it has provided an // alternate size that is as close to that as possible. } } ---- -=== Constraint Support By Platform +=== Constraint support by platform [width="100%",options="header,footer"] @@ -113,5 +112,5 @@ if (cnst.isSizeSupported()) { | Javascript (Android) | Yes | Yes | Yes | No | Javascript (iOS) | Yes | Yes | Yes | No |==================== -`*` If size is specified, the platform will attempt to translate to the appropriate quality constraint. +`*` If size is specified, the platform will try to translate to the appropriate quality constraint. diff --git a/docs/developer-guide/Working-With-CodenameOne-Sources.asciidoc b/docs/developer-guide/Working-With-CodenameOne-Sources.asciidoc index 405d753e84..d5e9dc68cc 100644 --- a/docs/developer-guide/Working-With-CodenameOne-Sources.asciidoc +++ b/docs/developer-guide/Working-With-CodenameOne-Sources.asciidoc @@ -1,4 +1,4 @@ -== Working with Codename One Sources +== Working with Codename One sources The Codename One SDK is published as a Maven multi-module project. Building the aggregator in the `/maven` directory compiles every module, installs the @@ -32,7 +32,7 @@ $ mvn install The default build runs every module and its unit tests. Append `-DskipTests` if you want to skip the test phases to speed up local builds. -The version printed by Maven will usually end with `-SNAPSHOT` when you build +The version printed by Maven will end with `-SNAPSHOT` when you build from the `master` branch. Release builds from Maven Central omit the suffix. === Installing the Maven archetypes @@ -67,7 +67,7 @@ $ mvn -pl core-unittests test $ mvn -pl tests -am verify ---- -Refer to the READMEs in `maven/core-unittests` and `maven/tests` for additional +Refer to the READMEs in `maven/core-unittests` and `maven/tests` for more configuration flags, platform requirements, and environment variables. === Using your local build in application projects @@ -89,12 +89,12 @@ project), adjust the `pom.xml` properties to reference your snapshot version: Open the project in your IDE and build or run it. Maven will resolve the local snapshot instead of downloading the latest release from Maven Central. -=== Why build from source? +=== Why build from source Building the SDK yourself gives you: * Immediate access to fixes and features before they reach Maven Central. -* The ability to inspect, debug, and modify the framework when you need custom +* The ability to inspect, debug, and change the framework when you need custom behavior. * A path to contribute improvements back to the Codename One core. diff --git a/docs/developer-guide/Working-With-Javascript.asciidoc b/docs/developer-guide/Working-With-Javascript.asciidoc index 5219e27f1f..fba62e505c 100644 --- a/docs/developer-guide/Working-With-Javascript.asciidoc +++ b/docs/developer-guide/Working-With-Javascript.asciidoc @@ -1,16 +1,16 @@ == Working with JavaScript -This section covers the Codename One Javascript port, which allows you to compile your app as native javascript and run it inside a browser. This is different from the https://www.codenameone.com/javadoc/com/codename1/ui/BrowserComponent.html[BrowserComponent] and other methods of displaying HTML/Javascript inside a Codename One app. +This section covers the Codename One Javascript port, which allows you to compile your app as native JavaScript and run it inside a browser. This is different from the https://www.codenameone.com/javadoc/com/codename1/ui/BrowserComponent.html[BrowserComponent] and other methods of displaying HTML/Javascript inside a Codename One app. [id="limitations", reftext="Limitations of the Javascript Port"] -=== Limitations of the Javascript Port +=== Limitations of the JavaScript port [id="static_initializers",reftext="Static Initializers"] -==== No Multithreaded Code inside Static Initializers +==== No multithreaded code inside static initializers NOTE: This section pertains to Codename One 3.6 and older. Newer versions of Codename One support multithreaded code inside static initializers now. -Codename One's Javascript port uses http://teavm.org/[TeaVM] to compile your application directly to Javascript so that it can run inside modern web browsers without the need for any plugins (i.e. NOT as an applet). One of the revolutionary features that TeaVM provides is the ability to run multi-threaded code (i.e. it has full support for Object.wait(), Object.notify(), Object.notifyAll(), and the `synchronized` keyword). The one caveat to be aware of is that **you can't use any threading primitives inside static initializers**. This is due to technical limitations in the browser environment and the way that TeaVM compiles class definitions into Javascript. The workaround for this issue is to do lazy initialization in cases where you need to use multithreaded code. +Codename One's Javascript port uses http://teavm.org/[TeaVM] to compile your application directly to Javascript so that it can run inside modern web browsers without the need for any plugins (that is, NOT as an applet). One of the revolutionary features that TeaVM provides is the ability to run multi-threaded code (that is, it has full support for Object.wait(), Object.tell(), Object.notifyAll(), and the `synchronized` keyword). The one caveat to be aware of is that **you can't use any threading primitives inside static initializers**. This is due to technical limitations in the browser environment and the way that TeaVM compiles class definitions into Javascript. The workaround for this issue is to do lazy initialization in cases where you need to use multithreaded code. **Example** @@ -39,9 +39,9 @@ class Class2 { } ---- -This fails because `Class2` calls Class1.getValue() in its static initializer, and `getValue()` calls `Log.p()`, which, underneath the covers, writes to Storage - which involves some synchronous network access in the Javascript port (i.e. it uses `wait()` and `notify()` under the hood. +This fails because `Class2` calls Class1.getValue() in its static initializer, and `getValue()` calls `Log.p()`, which, underneath the covers, writes to Storage - which involves some synchronous network access in the Javascript port (that is, it uses `wait()` and `notify()` under the hood. -If we simply remove the call to `Log.p()` from `getValue()`, as follows: +If you remove the call to `Log.p()` from `getValue()`, as follows: [source,java] ---- @@ -52,28 +52,28 @@ If we simply remove the call to `Log.p()` from `getValue()`, as follows: Then everything would be fine. -**But How do we Know if A method includes `wait()`/`notify` somewhere along the line?** +**But How do you Know if A method includes `wait()`/`notify` somewhere along the line?** When you try to build your app as a Javascript app, it will fail (if code in your static initializers uses `wait()`/`notify()` somewhere along the line). **How to Work Around this Issue** -Use lazy initialization wherever you can. You don't need to worry about this for setting static variables to literal values. E.g.: `static int someVal = 20;` will always be fine. But `static int someVal = OtherClass.calculateSomeVal();` may or may not be fine, because you don't know whether `calculateSomeVal()` uses a `wait/notify`. So instead of initializing `someVal` in the static initializer, create a static accessor that lazily initializes it. Or initialize it inside your app's `init()` method. Or initialize it inside the class constructor. +Use lazy initialization wherever you can. You don't need to worry about this for setting static variables to literal values. E.g.: `static int someVal = 20;` will always be fine. But `static int someVal = OtherClass.calculateSomeVal();` may or may not be fine, because you don't know whether `calculateSomeVal()` uses a `wait/notify`. instead of initializing `someVal` in the static initializer, create a static accessor that initializes it. Or initialize it inside your app's `init()` method. Or initialize it inside the class constructor. [id="troubleshooting", reftext="Troubleshooting Build Errors"] -=== Troubleshooting Build Errors +=== Troubleshooting build errors If your Javascript build fails, you should download the error log and see what the problem is. The most common errors are: 1. "[ERROR] Method XXX.()V is claimed to be synchronous, but it's has invocations of asynchronous methods" + -This error will occur if you've static initializers that use multithreaded code (e.g. wait/notify/sleep, etc...). See <> for information about troubleshooting this error. In some cases TeaVM may give a false-positive here (i.e. it *thinks* you're doing some multithreaded stuff, but you're not), then you can force the build to "succeed" by adding the `javascript.stopOnErrors=false` build hint. -2. "Method XXX was not found" +This error will occur if you've static initializers that use multithreaded code (for example, wait/tell/sleep, etc...). See <> for information about troubleshooting this error. Sometimes TeaVM may give a false-positive here (that is, it *thinks* you're doing some multithreaded stuff, but you're not), then you can force the build to "succeed" by adding the `javascript.stopOnErrors=false` build hint. +2. "Method XXX wasn't found" + -TeaVM uses its own Java runtime library. it's mostly complete, but you may occasionally run into methods that haven't been implemented. If you run into errors saying that certain classes or methods were not found, please post them to the https://github.com/codenameone/CodenameOne/issues[Codename One issue tracker]. You can also work around these by changing your own code to not use such functions. If this missing method doesn't fall on a critical path on your app, you can also force the app to still build despite this error by adding the `javascript.stopOnErrors=false` build hint. +TeaVM uses its own Java runtime library. it's complete, but you may occasionally run into methods that haven't been implemented. If you run into errors saying that certain classes or methods weren't found, please post them to the https://github.com/codenameone/CodenameOne/issues[Codename One issue tracker]. You can also work around these by changing your own code to not use such functions. If this missing method doesn't fall on a critical path on your app, you can also force the app to still build despite this error by adding the `javascript.stopOnErrors=false` build hint. [id="zip_war_preview", reftext="Deployment Formats"] -=== ZIP, WAR, or Preview. What's the difference? +=== ZIP, WAR, or preview. what's the difference The *Javascript* build target will result in up to three different bundles being generated: @@ -129,11 +129,11 @@ Some things to note in this file listing: 1. The *index.html* file is the entry point to the application. 2. *CORSProxy.class* is the proxy servlet for making network requests to other domains. -3. The *assets* directory contains all of your application's *jar* resources. All resource files in your app will end up in this directory. -4. The `teavm` directory contains all of the generated javascript for your application. Notice that there are some debugging files generated (*classes.js.map* and *classes.js.teavmdbg*). These aren't normally loaded by the browser when your app is run, but they can be used by Chrome when you're doing debugging. +3. The *assets* directory contains all your application's *jar* resources. All resource files in your app will end up in this directory. +4. The `teavm` directory contains all the generated JavaScript for your application. Notice that there are some debugging files generated (*classes.js.map* and *classes.js.teavmdbg*). These aren't loaded by the browser when your app is run, but they can be used by Chrome when you're doing debugging. 5. The *jar* files in the *WEB-INF/lib* directory are dependencies of the proxy servlet. They aren't required for your app to run - unless you're using the proxy. -**YourApp-1.0.zip** is appropriate for deploying the application on any web server. It contains all of the same files as the.war file, excluding the WEB-INF directory (i.e. it doesn't include any servlets, class files, or Java libraries - it contains purely client-side javascript files and HTML). +**YourApp-1.0.zip** is appropriate for deploying the application on any web server. It contains all the same files as the.war file, excluding the WEB-INF directory (that is, it doesn't include any servlets, class files, or Java libraries - it contains purely client-side JavaScript files and HTML). As an example, this is a listing of the files in the zip distribution of the PropertyCross demo: @@ -161,34 +161,34 @@ Archive: /path/to/PropertyCross-1.0.zip 4435321 1422743 68% 15 files ---- -You'll notice that it has many of the same files as the.war distribution. it's missing the the proxy servlet and dependencies. +You'll notice that it has many of the same files as the.war distribution. it's missing the proxy servlet and dependencies. -**YourApp-1.0-Preview.html** is a single-page HTML file with all of the application's resources embedded into a single page. This is generated for convenience so that you can preview your application on the build server directly. While you could use this file in production, you're probably better to use the ZIP or WAR distribution instead as some mobile devices have file size limitations that may cause problems for the "one large single file" approach. If you do decide to use this file for your production app (i.e. copy the file to your own web server), you will need to change the proxy settings, as it's configured to use the proxy on the Codename One build server - which won't be available when the app is hosted on a different server. +**YourApp-1.0-Preview.html** is a single-page HTML file with all the application's resources embedded into a single page. This is generated for convenience so that you can preview your application on the build server directly. While you could use this file in production, you're probably better to use the ZIP or WAR distribution instead as some mobile devices have file size limitations that may cause problems for the "one large single file" approach. If you do decide to use this file for your production app (that is, copy the file to your own web server), you will need to change the proxy settings, as it's configured to use the proxy on the Codename One build server - which won't be available when the app is hosted on a different server. [[javascript-proxy-settings]] -=== Setting up a Proxy for Network Requests +=== Setting up a proxy for network requests The Codename One API includes a network layer (the https://www.codenameone.com/javadoc/com/codename1/io/NetworkManager.html[NetworkManager] and https://www.codenameone.com/javadoc/com/codename1/io/ConnectionRequest.html[ConnectionRequest] classes) that allows you to make HTTP requests to arbitrary destinations. When an application is running inside a browser as a Javascript app, it's constrained by the same origin policy. You can make network requests to the same host that served the app originally. -E.g. If your application is hosted at http://example.com/myapp/index.html, then your app will be able to perform network requests to retrieve other resources under the *example.com* domain, but it won't be able to retrieve resources from *example2.com*, *foo.net*, etc.. +For example, If your application is hosted at http://example.com/myapp/index.html, then your app will be able to perform network requests to retrieve other resources under the *example.com* domain, but it won't be able to retrieve resources from *example2.com*, *foo.net*, etc.. NOTE: The HTTP standard does support cross-origin requests in the browser via the `Access-Control-Allow-Origin` HTTP header. Some web services supply this header when serving resources, but not all. The way to be make network requests to arbitrary resources is to do it through a proxy. -Luckily there is a solution. The.war javascript distribution includes an embedded proxy servlet, and your application is configured, by default, to use this servlet. If you intend to use the.war distribution, then it **should work**. You shouldn't need to do anything to configure the proxy. +There is a solution. The.war JavaScript distribution includes an embedded proxy servlet, and your application is configured, by default, to use this servlet. If you intend to use the.war distribution, then it **should work**. You shouldn't need to do anything to configure the proxy. -If, however, you're using the.zip distribution or the single-file preview, you will need to set up a Proxy servlet and configure your application to use it for its network requests. +If, but, you're using the.zip distribution or the single-file preview, you will need to set up a Proxy servlet and configure your application to use it for its network requests. -==== Step 1: Setting up a Proxy +==== Step 1: setting up a proxy TIP: This section is relevant if you're using the.zip or single-file distributions of your app. You shouldn't need to set up a proxy for the.war distribution since it includes a proxy built-in. -The easiest way to set up a proxy is to use the Codename One *https://github.com/shannah/cors-proxy[cors-proxy]* project. This is the open-source project from which the proxy in the.war distribution is derived. Simply download and install the cors-proxy.war file in your JavaEE compatible servlet container. +The easiest way to set up a proxy is to use the Codename One *https://github.com/shannah/cors-proxy[cors-proxy]* project. This is the open-source project from which the proxy in the.war distribution is derived. Download and install the cors-proxy.war file in your JavaEE compatible servlet container. If you don't want to install the.war file, but would rather copy the proxy servlet into an existing web project, you can do that also. https://github.com/shannah/cors-proxy/wiki/Embedding-Servlet-into-Existing-Project[See the cors-proxy wiki for more information about this]. -==== Step 2: Configuring your Application to use the Proxy +==== Step 2: configuring your application to use the proxy -There are three ways to configure your application to use your proxy. +three ways to configure your application to use your proxy. 1. Using the *javascript.proxy.url* build hint. + @@ -206,7 +206,7 @@ E.g.: window.cn1CORSProxyURL='http://example.com/myapp/cn1-cors-proxy?_target='; ---- -3. By setting the `javascript.proxy.url` property in your Java source. Generally you would do this inside your `init()` method, but it has to be executed before you make a network request that requires the proxy. +3. By setting the `javascript.proxy.url` property in your Java source. You would do this inside your `init()` method, but it has to be executed before you make a network request that requires the proxy. + ---- Display.getInstance().setProperty( @@ -217,20 +217,18 @@ Display.getInstance().setProperty( The method you choose will depend on the workflow that you prefer. Options #1 and #3 will almost always result in fewer changes than #2 because you've to set them up once, and the builds will retain the settings each time you build your project. -=== Using the CORS Proxy for Same Origin Requests +=== Using the CORS proxy for same origin requests -By default, the CORS proxy is used for HTTP requests to URLS at a different domain than the one that the app is running in. There are some circumstances where you may want to *even* use the proxy for same domain requests. You can do this by setting the `javascript.useProxyForSameDomain` display property to `true`. E.g. - -[source,java] +By default, the CORS proxy is used for HTTP requests to URLS at a different domain than the one that the app is running in. some circumstances where you may want to *even* use the proxy for same domain requests. You can do this by setting the `javascript.useProxyForSameDomain` display property to `true`. For example, [source,java] ---- Display.getInstance().setProperty("javascript.useProxyForSameDomain", "true"); ---- *Why would you want to do this?* -The browser shields some HTTP headers (e.g. "Set-Cookie") from Javascript so that your app can't access them. Going through the proxy works around this limitation by copying and encoding such headers in a format that the browser will allow, and then decoding them client-side to make them available to your app seamlessly. +The browser shields some HTTP headers (for example, "Set-Cookie") from Javascript so that your app can't access them. Going through the proxy works around this limitation by copying and encoding such headers in a format that the browser will allow, and then decoding them client-side to make them available to your app seamlessly. -==== Using Apache as a Proxy +==== Using apache as a proxy If you're hosting your application on an Apache 2 web server with mod_proxy installed, and you need to make CORS requests to a single domain (or a limited set of domains), you can use Apache to serve as your proxy. One sample configuration (which you would place either in your VirtualHost definition or your.htaccess file is as follows: @@ -240,9 +238,9 @@ ProxyPass /app https://www.myexternaldomain.com ProxyPassReverse /app https://www.myexternaldomain.com ---- -This tells Apache to proxy all requests for '/app' to the domain https://www.myexternaldomain.com. You would then need to set your CORS proxy URL in your CN1 app to "/app/". +This tells Apache to proxy all requests for '/app' to the domain https://www.myexternaldomain.com. You would then need to set your CORS proxy URL in your CN1 app to `/app/`. -The syntax is the same if you've multiple domains, but keep attention to the order of the lines to make the proxy working correctly. For example: +The syntax is the same if you've multiple domains, but keep attention to the order of the lines to make the proxy working. For example: ---- SSLProxyEngine on @@ -255,11 +253,11 @@ ProxyPassReverse /storage https://www.myexternaldomain2.com This tells Apache to proxy all requests for '/app' to the domain https://www.myexternaldomain1.com and all requests for '/storage' to the domain https://www.myexternaldomain2.com [id="splash_screen", reftext="Customizing the Splash Screen"] -=== Customizing the Splash Screen +=== Customizing the splash screen -Since your application may include many resource files, videos, etc.., the the build-server will generate a splash screen for your app to display while it's loading. This basically shows a progress indicator with your app's icon. +Since your application may include many resource files, videos, etc.., the build-server will generate a splash screen for your app to display while it's loading. This basically shows a progress indicator with your app's icon. -You can customize this splash screen by simply modifying the HTML source inside the *cn1-splash* `div` tag of your app's index.html file: +You can customize this splash screen by modifying the HTML source inside the *cn1-splash* `div` tag of your app's index.html file: [source,html] ---- @@ -274,7 +272,7 @@ You can customize this splash screen by simply modifying the HTML source inside [id="debugging", reftext="Debugging in Chrome"] === Debugging -If you run into problems with your app that occur in the Javascript version, you may need to do a little bit of debugging. There are many debugging tools for Javascript, but the preferred tool for debugging Codename One apps is Chrome's debugger. +If you run into problems with your app that occur in the Javascript version, you may need to do a little bit of debugging. many debugging tools for Javascript, but the preferred tool for debugging Codename One apps is Chrome's debugger. If your application crashes and you don't have a clue where to begin, follow these steps: @@ -288,23 +286,23 @@ image::img/chrome-debugger.png[Debugging using Chrome tools,scaledwidth=50%] [id="third_party_libs", reftext="Including Third-Party Javascript Libraries"] -=== Including Third-Party Javascript Libraries +=== Including Third-Party JavaScript libraries -Codename One allows you to interact directly with Javascript using native interfaces. Native interfaces are placed inside your project's `native/javascript` directory using a prescribed naming convention. If you want to, additionally, include third-party Javascript libraries in your application you should also place these libraries inside the `native/javascript` directory but you must specify which files should be treated as "libraries" and which files are treated as "resources". You can do this by adding a file with extension `.cn1mf.json` file either the root of your `native/javascript` directory or the root level of the project's `src` directory. +Codename One allows you to interact directly with Javascript using native interfaces. Native interfaces are placed inside your project's `native/JavaScript` directory using a prescribed naming convention. If you want to, include third-party Javascript libraries in your application you should also place these libraries inside the `native/JavaScript` directory but you must specify which files should be treated as "libraries" and which files are treated as "resources." You can do this by adding a file with extension `.cn1mf.json` file either the root of your `native/JavaScript` directory or the root level of the project's `src` directory. -==== Libraries vs Resources +==== Libraries vs resources -A *resource* is a file whose contents can be loaded by your application at runtime using `Display.getInstance().getResourceAsStream()`. In a typical Java environment, resources would be stored on the application's classpath (usually inside a Jar file). On iOS, resources are packaged inside the application bundle. In the Javascript port, resources are stored inside the `APP_ROOT/assets` directory. Historically, javascript files have always been treated as resources in Codename One, and many apps include HTML and Javascript files for use inside the https://www.codenameone.com/javadoc/com/codename1/ui/BrowserComponent.html[BrowserComponent]. +A *resource* is a file whose contents can be loaded by your application at runtime using `Display.getInstance().getResourceAsStream()`. In a typical Java environment, resources would be stored on the application's classpath (usually inside a Jar file). On iOS, resources are packaged inside the application bundle. In the Javascript port, resources are stored inside the `APP_ROOT/assets` directory. Historically, JavaScript files have always been treated as resources in Codename One, and many apps include HTML and Javascript files for use inside the https://www.codenameone.com/javadoc/com/codename1/ui/BrowserComponent.html[BrowserComponent]. With the Javascript port, it isn't quite so clear whether a Javascript file is meant to be a resource or a library that the application itself uses. Most of the time you probably want Javascript files to be used as libraries, but you might also have Javascript files in your app that are meant to be loaded at runtime and displayed inside a Web View - these would be considered resources. -==== The Javascript Manifest File +==== The JavaScript manifest file -In order to differentiate libraries from resources, you should provide a *cn1mf.json* file inside your `native/javascript` directory that specifies any files or directories that should be treated as libraries. This file can be named anything you like, as long as its name ends with `cn1mf.json`. Any files or directories that you list in this manifest file will be packaged inside your app's `includes` directory instead of the `assets` directory. Additionally it add appropriate ` @@ -328,13 +326,13 @@ NOTE: This also caused the `mylib1.js` file to be packaged inside the `includes` TIP: A project may contain more than one manifest file. This allows you to include manifest files with your cn1libs also. You need to make sure that each manifest file has a different name. -===== How to NOT generate the ` ---- -WARNING: Libraries included from a directory hierarchy may not work correctly with the single file preview that the build server generates. For that version, it will embed the contents of each included Javascript file inside the `index.html` file, but the rest of the directory contents will be omitted. If your the library depends on the directory hierarchy and supporting files and you require the single-file preview to work, then you may consider hosting the library on a separate server, and including the library directly from there, rather than embedding it inside your project's "native/javascript" directory. +WARNING: Libraries included from a directory hierarchy may not work with the single file preview that the build server generates. For that version, it will embed the contents of each included Javascript file inside the `index.html` file, but the rest of the directory contents will be omitted. If your the library depends on the directory hierarchy and supporting files and you require the single-file preview to work, then you may consider hosting the library on a separate server, and including the library directly from there, rather than embedding it inside your project's "native/JavaScript" directory. -===== Including Remote Libraries +===== Including remote libraries -The examples so far have demonstrated the inclusion of libraries that are part of the app bundle. However, you can also include libraries over the network by specifying the URL to the library directly. This is handy for including common libraries that are hosted by a CDN. +The examples so far have demonstrated the inclusion of libraries that are part of the app bundle. But, you can also include libraries over the network by specifying the URL to the library directly. This is handy for including common libraries that are hosted by a CDN. -E.g. The Google Maps library requires the Google maps API to be included. This is accomplished with the following manifest file contents: +For example, The Google Maps library requires the Google maps API to be included. This is accomplished with the following manifest file contents: ---- { @@ -399,7 +397,7 @@ E.g. The Google Maps library requires the Google maps API to be included. This i } ---- -NOTE: This example uses the "//" prefix for the URL instead of specifying the protocol directly. This allow the library to work for both http and https hosting. You could however specify the protocol as well: +NOTE: This example uses the "//" prefix for the URL instead of specifying the protocol directly. This allow the library to work for both http and https hosting. You could but specify the protocol as well: + ---- { @@ -411,13 +409,13 @@ NOTE: This example uses the "//" prefix for the URL instead of specifying the pr } ---- -===== Including CSS Files +===== Including CSS files -CSS files can be included using the same mechanism as is used for Javascript files. If the file name ends with ".css", then it will be treated as a CSS file (and included with a `` tag instead of a `