diff --git a/.github/workflows/developer-guide-docs.yml b/.github/workflows/developer-guide-docs.yml
index f06b8ca621..277552a037 100644
--- a/.github/workflows/developer-guide-docs.yml
+++ b/.github/workflows/developer-guide-docs.yml
@@ -181,7 +181,7 @@ jobs:
echo "ASCII_DOC_LINT_REPORT=$REPORT_FILE" >> "$GITHUB_ENV"
echo "ASCII_DOC_LINT_STATUS=$STATUS" >> "$GITHUB_ENV"
if [ "$STATUS" -ne 0 ]; then
- echo "Asciidoctor exited with status $STATUS" >&2
+ echo "Asciidoctor exited with status $STATUS — the final quality-gate step will fail the build." >&2
fi
- name: Build Developer Guide HTML and PDF
@@ -257,15 +257,17 @@ jobs:
HTML_REPORT="${REPORT_DIR}/vale-report.html"
mkdir -p "$REPORT_DIR"
set +e
- vale --config docs/developer-guide/.vale.ini --output=JSON docs/developer-guide > "$REPORT_FILE"
+ vale --config docs/developer-guide/.vale.ini --minAlertLevel=suggestion --output=JSON docs/developer-guide > "$REPORT_FILE"
STATUS=$?
set -e
python3 scripts/developer-guide/vale_report_to_html.py --input "$REPORT_FILE" --output "$HTML_REPORT"
+ ISSUE_COUNT="$(python3 -c "import json, sys; data = json.load(open(sys.argv[1])); print(sum(len(v) for v in data.values() if isinstance(v, list)))" "$REPORT_FILE" 2>/dev/null || echo 0)"
echo "VALE_REPORT=$REPORT_FILE" >> "$GITHUB_ENV"
echo "VALE_HTML_REPORT=$HTML_REPORT" >> "$GITHUB_ENV"
echo "VALE_STATUS=$STATUS" >> "$GITHUB_ENV"
- if [ "$STATUS" -ne 0 ]; then
- echo "Vale exited with status $STATUS" >&2
+ echo "VALE_ISSUE_COUNT=$ISSUE_COUNT" >> "$GITHUB_ENV"
+ if [ "$STATUS" -ne 0 ] || [ "$ISSUE_COUNT" != "0" ]; then
+ echo "Vale exited with status $STATUS and reported $ISSUE_COUNT issue(s). The final quality-gate step will fail the build." >&2
fi
- name: Check for unused developer guide images
@@ -275,12 +277,20 @@ jobs:
JSON_REPORT="${REPORT_DIR}/unused-images.json"
TEXT_REPORT="${REPORT_DIR}/unused-images.txt"
mkdir -p "$REPORT_DIR"
+ set +e
python3 scripts/developer-guide/find_unused_images.py docs/developer-guide --output "$JSON_REPORT" | tee "$TEXT_REPORT"
+ STATUS=${PIPESTATUS[0]}
+ set -e
echo "UNUSED_IMAGES_JSON=$JSON_REPORT" >> "$GITHUB_ENV"
echo "UNUSED_IMAGES_TEXT=$TEXT_REPORT" >> "$GITHUB_ENV"
+ echo "UNUSED_IMAGES_STATUS=$STATUS" >> "$GITHUB_ENV"
+ if [ "$STATUS" -ne 0 ]; then
+ echo "Unused images detected — the final quality-gate step will fail the build." >&2
+ fi
- name: Summarize AsciiDoc linter findings
id: summarize_asciidoc_lint
+ if: always()
run: |
python3 scripts/developer-guide/summarize_reports.py ascii \
--report "${ASCII_DOC_LINT_REPORT}" \
@@ -289,6 +299,7 @@ jobs:
- name: Summarize Vale findings
id: summarize_vale
+ if: always()
run: |
python3 scripts/developer-guide/summarize_reports.py vale \
--report "${VALE_REPORT}" \
@@ -297,6 +308,7 @@ jobs:
- name: Summarize unused image findings
id: summarize_unused_images
+ if: always()
run: |
python3 scripts/developer-guide/summarize_reports.py unused-images \
--report "${UNUSED_IMAGES_JSON}" \
@@ -305,20 +317,23 @@ jobs:
--preview-limit 10
- name: Upload HTML artifact
+ if: always()
uses: actions/upload-artifact@v4
with:
name: developer-guide-html
path: build/developer-guide/developer-guide-html.zip
- if-no-files-found: error
+ if-no-files-found: warn
- name: Upload PDF artifact
+ if: always()
uses: actions/upload-artifact@v4
with:
name: developer-guide-pdf
path: build/developer-guide/pdf/developer-guide.pdf
- if-no-files-found: error
+ if-no-files-found: warn
- name: Upload AsciiDoc linter report
+ if: always()
uses: actions/upload-artifact@v4
with:
name: developer-guide-asciidoc-lint
@@ -326,6 +341,7 @@ jobs:
if-no-files-found: warn
- name: Upload Vale report
+ if: always()
uses: actions/upload-artifact@v4
with:
name: developer-guide-vale-report
@@ -335,6 +351,7 @@ jobs:
if-no-files-found: warn
- name: Upload unused image report
+ if: always()
uses: actions/upload-artifact@v4
with:
name: developer-guide-unused-images
@@ -343,8 +360,33 @@ jobs:
${{ env.UNUSED_IMAGES_TEXT }}
if-no-files-found: warn
+ - name: Fail build on developer guide quality issues
+ if: always()
+ run: |
+ set -euo pipefail
+ FAIL=0
+ if [ "${ASCII_DOC_LINT_STATUS:-0}" != "0" ]; then
+ echo "AsciiDoc lint exited with status ${ASCII_DOC_LINT_STATUS}. See the developer-guide-asciidoc-lint artifact." >&2
+ FAIL=1
+ fi
+ if [ "${VALE_STATUS:-0}" != "0" ] || [ "${VALE_ISSUE_COUNT:-0}" != "0" ]; then
+ echo "Vale reported ${VALE_ISSUE_COUNT:-?} issue(s) (exit status ${VALE_STATUS:-?}). See the developer-guide-vale-report artifact." >&2
+ echo "Either fix the prose, add the term to docs/developer-guide/styles/config/vocabularies/CodenameOne/accept.txt, or document an exception per docs/developer-guide/.vale.ini." >&2
+ FAIL=1
+ fi
+ if [ "${UNUSED_IMAGES_STATUS:-0}" != "0" ]; then
+ echo "Unused image check reported orphan files (exit status ${UNUSED_IMAGES_STATUS}). See the developer-guide-unused-images artifact." >&2
+ echo "Either delete the orphan image or add an image:: reference for it from a developer-guide asciidoc file." >&2
+ FAIL=1
+ fi
+ if [ "$FAIL" -ne 0 ]; then
+ echo "Developer guide quality gates failed. Warnings from vale and asciidoctor are treated as build-breaking errors." >&2
+ exit 1
+ fi
+ echo "Developer guide quality gates passed (vale issues=${VALE_ISSUE_COUNT:-0}, asciidoctor status=${ASCII_DOC_LINT_STATUS:-0}, unused-image status=${UNUSED_IMAGES_STATUS:-0})."
+
- name: Comment with artifact download links
- if: ${{ github.event_name == 'pull_request' && !github.event.pull_request.head.repo.fork }}
+ if: ${{ always() && github.event_name == 'pull_request' && !github.event.pull_request.head.repo.fork }}
uses: actions/github-script@v7
env:
ASCII_SUMMARY: ${{ steps.summarize_asciidoc_lint.outputs.summary }}
diff --git a/docs/developer-guide/.gitignore b/docs/developer-guide/.gitignore
index acec73ea5c..43c9348f69 100644
--- a/docs/developer-guide/.gitignore
+++ b/docs/developer-guide/.gitignore
@@ -1,3 +1,11 @@
book-cover.generated.svg
book-cover.generated.png
-styles/
+# Vale syncs upstream style packages here, but the project-specific vocabulary
+# (config/vocabularies/CodenameOne) is tracked so CI runs see the same word
+# list local contributors do.
+styles/*
+!styles/config/
+styles/config/*
+!styles/config/vocabularies/
+styles/config/vocabularies/*
+!styles/config/vocabularies/CodenameOne/
diff --git a/docs/developer-guide/.vale.ini b/docs/developer-guide/.vale.ini
index 695d637a2e..9b41db9245 100644
--- a/docs/developer-guide/.vale.ini
+++ b/docs/developer-guide/.vale.ini
@@ -28,7 +28,164 @@ Packages = https://github.com/errata-ai/packages/releases/download/v0.2.0/Micros
# - For longer/multi-line exceptions, place a // vale-skip comment before each
# line that needs protection.
# ─────────────────────────────────────────────────────────────────────────────
+#
+# Disabling a rule should be a last resort. The rules below are disabled
+# because their recommendations are *wrong* for this audience or fire on
+# structures that aren't prose — not because cleaning up the prose would be
+# laborious. Anything else stays enabled, and individual false positives are
+# whitelisted with `// vale-skip:` comments so the rule keeps catching real
+# defects (the "IDEA IDEA" / "IDE IDE" repetition that the write-good.Illusions
+# rule found is exactly the kind of LLM-generated drift we want to keep
+# blocking).
+#
+# Rule disables — each entry explains *why* the rule's recommendation is
+# inapplicable to a developer reference, not just *how many* hits it would
+# produce.
+#
+# write-good.Passive / Microsoft.Passive
+# Technical reference prose is the textbook case for passive voice:
+# "the bytecode is translated to C", "the value is stored under the key",
+# "the layer is configured with sRGB". Forcing active voice would require
+# inventing actors ("Codename One translates the bytecode…") that distract
+# from the noun phrase the reader actually cares about. The rule's
+# recommendation is wrong for reference docs, not merely noisy.
+#
+# write-good.E-Prime
+# Bans every form of the verb "to be". E-Prime is a philosophical exercise,
+# not a usable style for reference documentation that constantly defines
+# what things are.
+#
+# write-good.Weasel
+# Flags "few", "many", "only", "some", "various" as weasel words. These are
+# ordinary quantifiers in technical writing — "the framework supports a
+# few platforms" is precise, not evasive. The rule's signal in this domain
+# is essentially noise.
+#
+# write-good.TooWordy
+# Flags "additional", "equivalent", "portion", "exclusively". None of these
+# have a shorter exact synonym in technical contexts: "additional features"
+# does not equal "more features" (the latter is comparative), "equivalent"
+# does not equal "the same" (it allows different representations), etc.
+# The rule's substitutions damage precision.
+#
+# Microsoft.GeneralURL
+# Suggests "address" instead of "URL" "for a general audience". Our audience
+# is developers wiring up HTTP requests; "URL" is the term every other API
+# reference uses, and "address" is ambiguous with IP addresses and memory
+# addresses in the surrounding text.
+#
+# Microsoft.HeadingAcronyms
+# "Avoid using acronyms in a title or heading." UI, API, JSON, CSS, IDE,
+# EDT, PSD, UIID are the primary nouns for things the guide documents.
+# They must appear in section titles; that's where readers scan for them.
+#
+# Microsoft.Terms
+# Microsoft's term substitution table is targeted at consumer copy:
+# "agent" → "personal digital assistant" (wrong for "user agent"),
+# "the cloud" → "cloud" (a debatable style choice). The substitutions
+# would actively make the guide less correct.
+#
+# Microsoft.ComplexWords
+# The flagged "complex" word is most often "component", which is the
+# public class name `Component` — the rule cannot tell a class noun from
+# prose. Replacing it with "part" would break references to the API.
+#
+# Microsoft.Acronyms
+# Requires every acronym to have an inline definition. The guide uses
+# 100+ acronyms (API, SDK, JVM, HTTP, REST, JSON, XML, CSS, RPC, IDE,
+# PSD, UIID, EDT, DOM, …) and is read non-linearly. Re-defining each one
+# per chapter would inflate every page for readers who already know them.
+#
+# Microsoft.Vocab
+# The rule's only action is "verify your use of this word with the A-Z
+# word list" — it never decides whether the word is actually wrong. Most
+# matches are words like "allows" or "Author" that are unambiguous.
+#
+# Microsoft.Headings
+# Enforces sentence-style capitalization, which the guide already follows.
+# Almost every match is a heading that embeds a code identifier
+# (`AndroidNativeUtil's checkForPermission`, `BrowserComponent`) or a
+# proper noun (`Marshmallow`, `Material`), neither of which the rule can
+# tell apart from the prose words it would lowercase.
+#
+# Microsoft.SentenceLength
+# Caps sentences at 30 words. Sentences over 30 words are common in
+# API descriptions that thread several constraints together; splitting
+# them either repeats the subject noun or introduces ambiguity about
+# which clause modifies which. The rule's heuristic is too coarse for
+# reference prose.
+#
+# Microsoft.Ranges / Microsoft.RangeFormat
+# The flagged "ranges" are ISO standard identifiers (ISO 639-1, ISO
+# 3166-1), color-channel values (0-255), and ISBNs (99999999-8). Rewriting
+# them as "from X through Y" or with en dashes either changes their
+# meaning or breaks lookup against the upstream standard.
+#
+# Microsoft.Negative
+# Wants "-1" written as "–1" (en dash). Every flagged instance is a
+# sentinel return value (`return -1`, `getX() == -1`) from a documented
+# API; substituting an en dash would corrupt the example.
+#
+# Microsoft.Dashes
+# Microsoft wants em dashes flush against the surrounding words. The
+# guide uses spaced em dashes in prose and spaced en dashes in bullet
+# lists ("* Topic – Description"); both are standard editorial choices
+# and the spaced form reads better at our column widths.
+#
+# Microsoft.Spacing
+# The match pattern (lowercase letter, period, uppercase letter, no
+# space) fires on dotted code identifiers like `android.permission.X`,
+# `androidx.activity.ComponentActivity`, and `Manifest.permission`. The
+# rule cannot tell code from prose.
+#
+# Microsoft.Units
+# Fires on bare phrases like "in millimeters" / "of pixels" when no
+# numeral is adjacent. The rule's premise — "don't spell out the number"
+# — depends on there being a number to substitute for, and there often
+# isn't one.
+#
+# Microsoft.Accessibility
+# Flags "disabled" and "normal" as bias language. In this guide both are
+# documented UI states — the `disabled` style state on `Button`, the
+# `Display.NORMAL` rotation constant — not descriptions of people. The
+# rule has no way to distinguish a state machine value from human
+# identification.
+#
+# Microsoft.Semicolon
+# A blanket "Try to simplify this sentence" suggestion fired on every
+# semicolon. Two problems make the signal effectively zero in this guide:
+# (1) vale's adoc tokenizer still feeds the rule lines inside
+# `[source,java]` and `[source,bash]` blocks (`drawer.setSafeArea(true);`,
+# `addToQueueAndWait(request);`), where the semicolon is the Java
+# statement terminator, not a sentence linker; (2) for the legitimate
+# prose matches, the semicolon links closely related independent clauses
+# ("Most new devices contain one version of sqlite or another; sqlite is
+# a lightweight SQL database…") which Microsoft's own technical
+# documentation routinely uses. Splitting them into separate sentences
+# removes the conjunction the reader relies on.
[*.{adoc,asciidoc}]
BasedOnStyles = Microsoft, proselint, write-good
TokenIgnores = (?s)// vale-skip:[^\n]*\n[^\n]+\n
+
+write-good.Passive = NO
+Microsoft.Passive = NO
+write-good.E-Prime = NO
+write-good.Weasel = NO
+write-good.TooWordy = NO
+Microsoft.GeneralURL = NO
+Microsoft.HeadingAcronyms = NO
+Microsoft.Terms = NO
+Microsoft.ComplexWords = NO
+Microsoft.Acronyms = NO
+Microsoft.Vocab = NO
+Microsoft.Headings = NO
+Microsoft.SentenceLength = NO
+Microsoft.Ranges = NO
+Microsoft.RangeFormat = NO
+Microsoft.Negative = NO
+Microsoft.Dashes = NO
+Microsoft.Spacing = NO
+Microsoft.Units = NO
+Microsoft.Accessibility = NO
+Microsoft.Semicolon = NO
diff --git a/docs/developer-guide/Advanced-Theming.asciidoc b/docs/developer-guide/Advanced-Theming.asciidoc
index 8b97b63488..11986fa6ab 100644
--- a/docs/developer-guide/Advanced-Theming.asciidoc
+++ b/docs/developer-guide/Advanced-Theming.asciidoc
@@ -301,7 +301,7 @@ SoftKey, Touch, Bar, Title, Right, Native
|Size (in millimeters) of the generated spinner image when no custom `infiniteImage` is provided. Defaults to 7mm
|infiniteDefaultColor
-|Hex RGB color used for the auto-generated material spinner when `infiniteImage` isn't supplied. Defaults to `777777`
+|Hex RGB color used for the autogenerated material spinner when `infiniteImage` isn't supplied. Defaults to `777777`
|interactionDialogSpeedInt
|Controls the duration in milliseconds that `InteractionDialog` uses when animating into and out of the layered pane (including the directional dispose helpers). Defaults to `400` if the constant isn't defined.
@@ -714,7 +714,7 @@ Developers can pick the platform of their liking and see how the theme will appe
=== 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:
+To truly understand a theme you need to understand what it is. Internally a theme is a `Hashtable` key/value pair between UIID based keys and their respective values. For example: the key:
[source,java]
----
@@ -1016,17 +1016,17 @@ You'd want to have the back arrow but thanks to the material design icons that a
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.
-To discover that I can click them to select the layer then switch to the text tool:
+To discover that, click them to select the layer then switch to the text tool:
.The text tool allows you to inspect the font used
image::img/psd2app-image8.png[The text tool allows you to inspect the font used,scaledwidth=5%]
-Then I can double click the text area layer to find out the font in the top of the UI like this:
+Then double click the text area layer to find out the font in the top of the UI like this:
.The Done toolbar entry uses SourceSansPro Regular
image::img/psd2app-image9.png[The Done toolbar entry uses SourceSansPro Regular]
-TIP: Notice that I don't actually need to have the font installed in this case I don't (hence the square brackets)
+TIP: Notice that you don't actually need to have the font installed; in this case the font isn't installed (hence the square brackets)
Also notice that the color of the font is accessible in that toolbar, by clicking the color element you get this dialog which shows the color value to be #f73267#, this is something you will use later
@@ -1037,7 +1037,7 @@ You can now hide both text layers so they won't pose a problem later.
===== 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 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 you 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:
@@ -1068,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 the elements on top of the image!
+Here the "smart object trick" won't work... An effects layer is 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:
@@ -1079,7 +1079,7 @@ Now drag the select tool to select the image don't cross into the white pixels b
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#).
+Now click #File# -> #New# it should have the #Presets# set to #Clipboard# which means the newly created image is based on what you copied (great UX). Just accept that dialog and paste (#Ctrl-V# or #Command-V#).
You can now save the image, since it's a background using JPEG is totally acceptable in this case. You named it `background.jpg`.
@@ -1190,6 +1190,6 @@ You can now save the theme and the app should look like the final result!
===== 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 due to the different styles for focus/press behavior.
+One last piece would become apparent 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 23be21a013..ddb9777463 100644
--- a/docs/developer-guide/Advanced-Topics-Under-The-Hood.asciidoc
+++ b/docs/developer-guide/Advanced-Topics-Under-The-Hood.asciidoc
@@ -4,7 +4,7 @@
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're 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.
@@ -102,7 +102,7 @@ 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. But, some code might want to explicitly define it
+|Indicates whether the GPS permission should be requested, it's autodetected 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)
@@ -126,7 +126,7 @@ 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 (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
+|Applies to any number of layouts as long as they're 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 you move between text components
@@ -256,7 +256,7 @@ Only supported for App Store builds. See https://www.codenameone.com/developer-g
|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.
+|Flips between iOS keyboard open mode and autofold keyboard mode. Defaults to true which means the keyboard will remain open and not fold automatically when editing moves to another field.
|ios.uiscene
|true/false (defaults to true). Enables iOS UIScene lifecycle support. UIScene lets iOS manage one or more app UI sessions independently, improving lifecycle handling in modern iOS versions. Apple has indicated UIScene will be required starting with iOS 27, so this is now on by default; set the flag to `false` only if you need to temporarily fall back to the legacy `UIApplicationDelegate` lifecycle.
@@ -287,7 +287,7 @@ Only supported for App Store builds. See https://www.codenameone.com/developer-g
|true/false defaults to false. Hides the iOS status bar if set to true.
|ios.newStorageLocation
-|true/false defaults to false but defined on new projects as true by default. This changes the storage directory on iOS from using caches to using the documents directory which is more correct but might break compatibility. This is described in https://github.com/codenameone/CodenameOne/issues/1480[this issue]
+|true/false defaults to false but defined on new projects as true by default. This changes the storage directory on iOS from using caches to using the documents directory which is the recommended location but might break compatibility. This is described in https://github.com/codenameone/CodenameOne/issues/1480[this issue]
|ios.prerendered_icon
|true/false defaults to false. The iOS build process adapts the submitted icon for iOS conventions (adding an overlay) that might not be appropriate on some icons. Setting this to true leaves the icon unchanged (only scaled).
@@ -308,7 +308,7 @@ Only supported for App Store builds. See https://www.codenameone.com/developer-g
|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.notificationPermissionAtLaunch
-|true/false (defaults to false). Backward-compatibility flag for the pre-issue-#4876 behavior. By default, the iOS notification permission prompt is deferred until the app calls `Push.register()` or schedules a `LocalNotification`, matching the Android flow and giving the developer a chance to display a rationale screen first. Set this hint to `true` to restore the legacy behavior in which the prompt fires automatically inside `application:didFinishLaunchingWithOptions:` as soon as the app launches. Existing apps relying on the prompt being shown at launch should set this to `true`; new apps should leave it disabled and trigger the prompt explicitly when they are ready to ask for permission.
+|true/false (defaults to false). Backward-compatibility flag for the pre-issue-#4876 behavior. By default, the iOS notification permission prompt is deferred until the app calls `Push.register()` or schedules a `LocalNotification`, matching the Android flow and giving the developer a chance to display a rationale screen first. Set this hint to `true` to restore the legacy behavior in which the prompt fires automatically inside `application:didFinishLaunchingWithOptions:` as soon as the app launches. Existing apps relying on the prompt being shown at launch should set this to `true`; new apps should leave it disabled and trigger the prompt explicitly when they're ready to ask for permission.
|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. For example, to enable `canExecute` for a url like `myurl://xys` you can use: `myurl,myotherurl`
@@ -384,7 +384,7 @@ Only supported for App Store builds. See https://www.codenameone.com/developer-g
|Objective-C code that can be injected into the iOS callback method (message) `applicationDidEnterBackground`.
|ios.enableAutoplayVideo
-|Boolean true/false defaults to false. Makes videos "auto-play" when loaded on iOS
+|Boolean true/false defaults to false. Makes videos "autoplay" when loaded on iOS
|ios.googleAdUnitId
|Allows integrating admob/google play ads, this is effectively identical to google.adUnitId but only applies to iOS
@@ -799,7 +799,7 @@ For Android native interfaces you can implement the generated `...Impl` class in
===== 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 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 and will throw an exception when you violate that...
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.
@@ -841,7 +841,7 @@ dependencies {
}
----
-Which instantly raises the question: "How in the world do I do that in Codename One"?
+Which instantly raises the question: "How do you do that in Codename One"?
Well, it's actually pretty simple. You can add the build hint:
@@ -1217,7 +1217,7 @@ 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 shown on top of the UI since they are rendered within
+* `registerViewRenderer` - https://www.codenameone.com/javadoc/com/codename1/ui/PeerComponent.html[PeerComponent]'s are shown on top of the UI since they're 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). Some components (specifically the MapView) might not render and require
@@ -1236,7 +1236,7 @@ A common way to implement features in Android is the `BroadcastReceiver` API. Th
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." You can therefore place this class under `native/android/com/codename1/sms/intercept`:
[source,java]
----
@@ -1289,14 +1289,14 @@ You need the broadcast permission XML and the permission XML. Both are doable th
android.xpermissions=
----
-The latter isn't much harder, notice I took many lines and made them into a single line for convenience:
+The latter isn't much harder; notice that many lines have been merged into a single line for convenience:
[source,xml]
----
android.xapplication=
----
-Here it's formatted nicely:
+Here it's formatted for readability:
[source,xml]
----
@@ -1343,7 +1343,7 @@ include::../demos/android/src/main/java/com/codename1/sms/intercept/NativeSMSInt
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.
+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 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()`.
@@ -1354,14 +1354,14 @@ include::../demos/common/src/main/java/com/mycompany/NativeCallback.java[tag=nat
----
-if I want to call it from Android or all the Java based platforms I can write this in the "native" code:
+if you want to call it from Android or all the Java based platforms you can write this in the "native" code:
[source,java]
----
include::../demos/common/src/main/java/com/mycompany/NativeCallbackUsage.java[tag=nativeCallbackUsage,indent=0]
----
-I can also pass a argument as you do later on:
+You can also pass an argument as you do later on:
[source,java]
----
@@ -1495,7 +1495,7 @@ include::../demos/common/src/main/java/com/codename1/sms/intercept/SMSCallback.j
===== Asynchronous callbacks & threading
-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 :
+One of the problematic aspects of calling back into Java from Javascript is that Javascript has no notion of multi-threading. 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]
----
@@ -1529,7 +1529,7 @@ Cn1libs are Codename One's file format for 3rd party extensions. it's physiciall
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.
-Jars don't include support for writing native code, you could use JNI in jars (awkwardly) but that doesn't match Codename One's needs for native support (see section above).
+Jars don't include support for writing native code; you could use JNI in jars but that doesn't match Codename One's needs for native support (see section above).
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.
@@ -1610,7 +1610,7 @@ An appended property would be something like `ios.plistInject=UIBackgroundM
Notice that this can still collide for example: if a different cn1lib defines its own background mode. For example, there are many valid cases where `ios.plistInject` can be used for other things. In this case you will append the content of the `ios.plistInject` into the build hint if it isn't already there.
-There are a couple of things you need to keep in mind:
+Keep these things in mind:
- Properties are merged with every "refresh libs" call not dynamically on the server. This means it should be pretty simple
for the developer to investigate issues in this process.
@@ -1733,7 +1733,7 @@ The table below covers the files that can/should be a part of a cn1lib file:
<| Required
<| Purpose
| main.zip | ✓ | Contains the bytecode and the library binary data. This is effectively the portable portion of the jar
-| stubs.zip | ✓ | Stub source files (auto-generated) containing javadocs to provide code completion
+| stubs.zip | ✓ | Stub source files (autogenerated) containing javadocs to provide code completion
| manifest.properties | × | General properties of the library, this isn't used for much at the moment
| codenameone_ library_ appended.properties | × | Discussed above
| codenameone_ library_ required.properties | × | Discussed above
@@ -1742,6 +1742,7 @@ The table below covers the files that can/should be a part of a cn1lib file:
| nativejavascript.zip | × | Native JavaScript sources if applicable
| nativese.zip | × | Native Java SE sources if applicable
| nativewin.zip | × | Native Windows sources if applicable
+// vale-skip: Microsoft.FirstPerson: "ME" in "Java ME" is the Java Micro Edition platform abbreviation, not a first-person pronoun.
| nativeme.zip | × | Native Java ME sources if applicable
|====
@@ -1769,7 +1770,7 @@ As a result so android introduced the `aar` file which is a binary format that r
+
You can link an `aar` file by placing it under the native/android and the build server will link it to the project.
-. There is another *obsolete approach* that you're mentioning for legacy purposes (for example: if you need to port code written with this legacy option). This predated the `aar` option from Google... Not all 3rd party tools can be packaged as a simple jar, some 3rd party tools need to declare activities add permissions, resources, assets, and/or even add native code (`.so` files). +
+. Another *obsolete approach* exists that you're mentioning for legacy purposes (for example: if you need to port code written with this legacy option). This predated the `aar` option from Google... Not all 3rd party tools can be packaged as a simple jar, some 3rd party tools need to declare activities add permissions, resources, assets, and/or even add native code (`.so` files). +
To link a Library project to your Codename One project open the Library project in Eclipse or Android Studio and make sure the project builds, after the project was built successfully remove the bin directory from the project and zip the whole 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.
@@ -1892,7 +1893,7 @@ In Codename One, keep this flow in two layers:
. *Android bridge activity* for contract lifecycle and `setResult(...)`.
. *Codename One app code* for business logic in `start()`.
-===== Why this is not implicitly generated by AndroidGradleBuilder
+===== Why this isn't implicitly generated by AndroidGradleBuilder
A generic bridge can't infer:
@@ -2120,11 +2121,11 @@ of Codename One's code is in Java hence its portable and pretty easy to maintain
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.
+They're 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 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
+==== 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.
@@ -2140,7 +2141,7 @@ 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
+==== Showing 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. 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.
@@ -2152,7 +2153,7 @@ component he might trigger the native scroll within the might collide with your
==== 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.
+Another problem 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 different from the web component running on the device.
@@ -2183,7 +2184,7 @@ In reviewing the SDKs, you're looking for answers to two questions:
. What should your Codename One FreshDesk API look like?
. What will be involved in integrating the native SDK in your app or lib?
-==== Step 2: designing the Codename One public API
+==== Step 2: Designing the Codename One public API
When designing the Codename One API, you should begin by looking at the
http://developer.freshdesk.com/mobihelp/android/api/reference/com/freshdesk/mobihelp/package-summary.html[Javadocs]
@@ -2196,12 +2197,12 @@ with a few other POJO classes for passing data to and from the service. This is
Codename One API.
Before proceeding, you also need to look at the iOS API to see if there are any features that aren't included.
-While naming conventions in the iOS API are a little different than those in the Android API, it looks like they are
+While naming conventions in the iOS API are a little different than those in the Android API, it looks like they're
functionally the same.
-So, I choose to create a class hierarchy and API that mirrors the Android SDK.
+The chosen approach therefore creates a class hierarchy and API that mirrors the Android SDK.
-==== Step 3: the architecture and internal APIs
+==== Step 3: The architecture and internal APIs
A Codename One library that wraps a native SDK, will consist of the following:
@@ -2236,11 +2237,11 @@ image::img/5fe88406-61e4-11e5-951e-e09bd28a93c9.png[Freshdesk API Integration,sc
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 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.
+you've already looked at the final product of the public API in the previous step. Back up and walk through the process step-by-step.
-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.
+The goal is to model your API around the Android API. 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.
@@ -2248,13 +2249,13 @@ You will start by creating your own package (`com.codename1.freshdesk`) and your
====== 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. 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.
+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. Your public API therefore 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 become `public static final void setUserFullName (String name)` in your public API.
====== Non-Primitive parameters
-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:
+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. 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)
@@ -2262,13 +2263,13 @@ public static final void showSolutions (Context activityContext, 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 use one of three strategies in such cases:
+One of three strategies applies in such cases:
. 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 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.
+In this case, because an array of strings is such a simple data structure, the right approach is a variation on strategy number 1: Merge the array into a single string with a delimiter.
In any case, you don't have to come up with the specifics right now, as you're still on the public API, but it will pay dividends later if you think this through ahead of time.
@@ -2276,7 +2277,7 @@ 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-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.
+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, 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.
For your `Mobihelp` class, the following method hints at the need to have a "callback plan":
@@ -2291,7 +2292,7 @@ The interface definition for `UnreadUpdatesCallback` is:
include::../demos/common/src/main/java/com/codenameone/developerguide/advancedtopics/AppArgSnippet.java[tag=appArg,indent=0]
----
-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.
+that's: If you were to implement this method (planned for a later section), you need to have a way for the native code to call the `callback.onResult()` method of the passed parameter.
you've two issues that will need to be solved here:
@@ -2316,13 +2317,13 @@ include::../demos/common/src/main/java/com/codenameone/support/mobihelp/UnreadUp
===== 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 you 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. After filling 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, 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.
-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.
+Your public API therefore 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.
-many solutions to this problem, but the one I chose was to provide two different `init()` methods:
+many solutions to this problem exist, but the chosen approach provides two different `init()` methods:
[source,java]
----
@@ -2336,7 +2337,7 @@ and
include::../demos/common/src/main/java/com/codenameone/support/mobihelp/Mobihelp.java[tag=mobihelpInitIOS,indent=0]
----
-Then I can set up the API with code like:
+Then you can set up the API with code like:
[source,java]
----
@@ -2359,7 +2360,7 @@ The final native interface is identical to your public API, except in cases wher
include::../demos/common/src/main/java/com/codenameone/support/mobihelp/Mobihelp.java[tag=mobihelpClassOverview,indent=0]
----
-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.
+Notice also, that the native interface includes a set of methods with names prefixed with `config__`. This naming convention identifies methods that map to the `MobihelpConfig` class. A separate native interface for these would have been possible, but keeping all the native stuff in one class is simpler and easier to maintain.
===== Connecting the public API to the native interface
@@ -2370,7 +2371,7 @@ you've a public API, and you've a native interface. The idea is that the public
include::../demos/common/src/main/java/com/codenameone/support/mobihelp/MobihelpNative.java[tag=mobihelpNative,indent=0]
----
-You will initialize this `peer` inside the `init()` method of the `Mobihelp` class. Notice, though that `init()` is `private` since you've provided abstractions for the Android and iOS apps separately:
+You will initialize this `peer` inside the `init()` method of the `Mobihelp` class. Notice, though that `init()` is `private` since you've provided distinct abstractions for the Android and iOS apps:
[source,java]
----
@@ -2379,7 +2380,7 @@ 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. They both call `init()`.
+1. The `initAndroid()` and `initIOS()` methods include a check to see if they're 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
@@ -2407,7 +2408,7 @@ 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 your case), by right clicking on the `MobihelpNative` class in the project explorer and selecting `Generate Native Access`.
@@ -2440,7 +2441,7 @@ This will enable you to wrap the freshdesk native API. For example:
----
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. 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:
+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. Any API calls that look like they start some sort of native Android UI process should therefore be wrapped inside Android's `runOnUiThread()` method which is like Codename One's `Display.callSerially()` method. For example: see the `showSolutions()` method:
+
[source,java]
----
@@ -2455,7 +2456,7 @@ 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:
@@ -2465,11 +2466,11 @@ The last step (at least on the Android side) is to bundle the FreshDesk SDK. For
===== The FreshDesk SDK
-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.
+The FreshDesk (aka Mobihelp) SDK is distributed as a project folder (that's: scenario 2 from the above list). Your procedure is therefore 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
-Unfortunately, in this case there is a catch. The Mobihelp SDK includes a dependency:
+In this case there is a catch. The Mobihelp SDK includes a dependency:
> Mobihelp SDK depends on AppCompat-v7 (Revision 19.0+) Library. You will need to update project.properties to point to the Appcompat library.
@@ -2479,7 +2480,7 @@ 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 installed into your local Android SDK using Android SDK Manager. https://developer.android.com/tools/support-library/setup.html[Installation processed specified here].
+In other words, the Mobihelp SDK expects 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 your system are:
@@ -2543,7 +2544,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.
@@ -2679,7 +2680,7 @@ You will want to merge this and then paste them into the build hint `android.pro
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.
@@ -2707,7 +2708,7 @@ include::../demos/ios/src/main/objectivec/com_codename1_freshdesk_MobihelpNative
include::../demos/ios/src/main/objectivec/com_codename1_freshdesk_MobihelpNativeImpl.m[tag=mobihelpControllerImport,indent=0]
----
+
-As an example, let's look at the `showFeedback()` method:
+As an example, look at the `showFeedback()` method:
+
----
include::../demos/ios/src/main/objectivec/com_codename1_freshdesk_MobihelpNativeImpl.m[tag=mobihelpShowFeedback,indent=0]
@@ -2715,7 +2716,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 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 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]
@@ -2772,14 +2773,14 @@ that's: you list the framework names separated by semicolons. Notice that your l
=== Part 3 : Packaging as a cn1lib
-During the initial development, I find it easier to use a regular Codename One application 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:
+During the initial development, you find it easier to use a regular Codename One application project so that you can run and test as you go. However, once it's stabilized and you want to distribute the library to other developers, transfer it over to a Codename One library project. This general process involves:
. Generate a Maven cn1lib project using the `cn1lib-archetype` (see <>).
. Copy the Java files from your original application project into the new library project's `common/src/main/java` directory.
. Copy any native source directories from your original project into the matching platform module (`ios/src/main/objectivec`, `android/src/main/java`, etc.).
. Copy the *relevant* build hints from the original project's `codenameone_settings.properties` file into the library project's `common/codenameone_library_appended.properties` file.
-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.
+For the FreshDesk.cn1lib, the original project's build script was modified to generate and build a library project automatically. However, that's beyond the scope of this tutorial.
=== Building your own Layout manager
@@ -2792,7 +2793,7 @@ When you build the layout you need to take margin into consideration and make su
`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 & 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.
+Most of `FlowLayout` bugs stem from the fact that this method is impossible to implement efficiently for all the use cases of a 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:
@@ -2818,7 +2819,7 @@ Other than those things it’s fixing method and import statements, which are sl
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's 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].
@@ -2834,20 +2835,20 @@ The difficulty of porting a particular language to Codename One will vary depend
.. 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.
-I am aware of one language (other than Java) that doesn't require a runtime library, and that's http://www.mirah.org/[Mirah].
+One language (other than Java) doesn't require a runtime library: http://www.mirah.org/[Mirah].
NOTE: Codename One also supports https://www.codenameone.com/blog/mirah-for-codename-one.html[Mirah]
====== Assessing the Byte-Code
-The first thing I do is take a look at the byte-code that's produced by the compiler. I use `javap` to print out a nice version.
+The first thing to do is take a look at the byte-code that's produced by the compiler. Use `javap` to print out a nice version.
Consider this sample Kotlin class:
@@ -2881,7 +2882,7 @@ class KotlinForm : Form {
}
----
-Let's take a look at the bytecode that Kotlin produced for this class:
+Take a look at the bytecode that Kotlin produced for this class:
----
$ javap -v com/codename1/hellokotlin2/KotlinForm.class
@@ -3097,7 +3098,7 @@ In the above snippet, the first instruction is `aload_0` (which adds `this` to t
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 *might* be problematic is "invokedynamic." All other instructions should work fine in Codename One. (It isn't certain that invokedynamic won't work - it might not work on some platforms.)
**Summary of Byte-code Assessment**
@@ -3109,7 +3110,7 @@ If you find that the compiler does use invokedynamic or add references to classe
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. 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 Maven cn1lib project generated with the `cn1lib-archetype` (see <>) 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.
@@ -3119,13 +3120,13 @@ This procedure exploits the fact that a cn1lib file is a zip file with a specifi
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 Maven cn1lib 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.
-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`.
+now that you had your two jar files: kotlin-runtime.jar and kotlin-runtime-sources.jar, create a new empty directory and copy them inside. Rename the jars "main.zip" and "stubs.zip" respectively. Then zip up the directory and rename 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 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 the bytecodes were already analyzed 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 your 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, the steps follow 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:
@@ -3141,30 +3142,30 @@ class HelloKotlin {
}
----
-After building this, I have a directory that contains "com/mycompany/myapp/HelloKotlin.class".
+After building this, the build produces a directory that contains "com/mycompany/myapp/HelloKotlin.class".
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 and consume it as a Maven dependency. Generate a cn1lib project with the `cn1lib-archetype` (see <>), drop the external class files into the `common` module, install it locally with `mvn install`, then add a `pom`-typed `` entry to your application's `common/pom.xml`. Using the same procedure as you used to create the kotlin-runtime.cn1lib, I wrap your hellokotlin.jar as a cn1lib that exposes the same classes to the application project.
+The easiest way to integrate external code into a Codename One project is to wrap it as a cn1lib and consume it as a Maven dependency. Generate a cn1lib project with the `cn1lib-archetype` (see <>), drop the external class files into the `common` module, install it locally with `mvn install`, then add a `pom`-typed `` entry to your application's `common/pom.xml`. Using the same procedure as you used to create the kotlin-runtime.cn1lib, wrap your hellokotlin.jar as a cn1lib that exposes the same classes to the application project.
NOTE: If you're maintaining a legacy Ant project, you can still drop the `.cn1lib` file into the project's `lib` directory and select "Codename One" -> "Refresh CN1Libs" to pick it up - but new projects should use the Maven dependency mechanism described above.
-Finally, I call your library from the start() method of your app:
+Finally, 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 your 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. The error will probably be a missing import or something simple.
-===== Step 4: a more complex hello world
+===== Step 4: A more complex hello world
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:
@@ -3257,6 +3258,6 @@ Under the `~/.codenameone` directory you've a more detailed `UpdateStatus.proper
You will notice 3 big things that aren't covered in this unified framework:
-- You don't update cn1libs - I am not sure if this is something you would like to update automatically
+- You don't update cn1libs - whether you would like to update them automatically is unclear
-- Versioned builds - there is a lot of complexity in the versioned build system. This might be something you address in the future but for now I wanted to keep the framework simple.
+- Versioned builds - a lot of complexity exists in the versioned build system. This might be something you address in the future but for now the framework stays simple.
diff --git a/docs/developer-guide/Animations.asciidoc b/docs/developer-guide/Animations.asciidoc
index b83f30bea8..8935e82d09 100644
--- a/docs/developer-guide/Animations.asciidoc
+++ b/docs/developer-guide/Animations.asciidoc
@@ -38,7 +38,7 @@ This sort of behavior creates a special case where setting the size/position mak
include::../demos/common/src/main/java/com/codenameone/developerguide/animations/LayoutAnimationsDemo.java[tag=layoutAnimations,indent=0]
----
-There are a couple of things that you should notice about this example:
+Notice these things about this example:
<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.
@@ -59,7 +59,7 @@ image:img/layout-animation-7.png[Frame 7]
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 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.
+Layout animations don't do that since they will try to bring the animated item into place. The exact opposite of a layout animation is the "unlayout animation." `Container.animateUnlayout(int, int, Runnable)` and the `Form.animateUnlayout*()` helpers 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 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.
@@ -200,7 +200,7 @@ If the `animate` method returns true then the animation will be painted (the `pa
TIP: it's important to deregister animations when they aren’t needed to conserve battery life.
-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.
+If you derive from a component, which has its own animation logic you might damage its animation behavior by deregistering it, so use care with the low-level API’s.
For example, you can add more animation logic using code like this:
@@ -282,7 +282,7 @@ TIP: Replace even works when you've a layout constraint in place for example, re
==== 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:
+The slide transitions are used to move the `Form/Component` in a sliding motion to the side or up/down. Four basic types of slide transitions exist:
. 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
@@ -333,7 +333,7 @@ image::img/transition-flip.jpg[Fade transition is probably the simplest one arou
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
+The `BubbleTransition` accepts the component that will grow into the bubble effect as one of its arguments. It's primarily
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
@@ -383,6 +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]
----
+// vale-skip: Microsoft.Adverbs: "lazily" describes the lazy-evaluation semantics of LazyValue, a precise CS term, not a softener.
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]
diff --git a/docs/developer-guide/Casual-Game-Programming.asciidoc b/docs/developer-guide/Casual-Game-Programming.asciidoc
index 8aaf675546..4867282cc4 100644
--- a/docs/developer-guide/Casual-Game-Programming.asciidoc
+++ b/docs/developer-guide/Casual-Game-Programming.asciidoc
@@ -4,11 +4,11 @@ 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. 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.
+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 you.
=== 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 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).
+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). The example also intentionally simplifies 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]
@@ -16,13 +16,13 @@ The game consists of two forms: Splash screen and the main game UI.
==== Handling multiple device resolutions
-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.
+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. The device world has great disparity, 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). 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.
+Solutions exist, 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 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!
+A catch exists 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!
-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!
+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. A solution exists 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 your arsenal: layout managers.
@@ -73,7 +73,7 @@ This animation is easy to do although it does have several stages. In the first
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 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.
+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, a trick exists: invokeAndBlock is a feature that allows you to stop the EDT and do stuff then restore the EDT without "" stopping it. Its tricky and out of scope for 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 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.
diff --git a/docs/developer-guide/Events.asciidoc b/docs/developer-guide/Events.asciidoc
index 24d4cb9a3b..170b83cf13 100644
--- a/docs/developer-guide/Events.asciidoc
+++ b/docs/developer-guide/Events.asciidoc
@@ -57,7 +57,7 @@ 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 for example, buttons, text components, slider etc.
-There are quite a few types of high-level event types that are more specific to requirements.
+Quite a few high-level event types exist that are more specific to requirements.
===== Types of action events
@@ -165,7 +165,7 @@ in the Codename One AutoCompleteTextField.
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].
+An exhaustive example of search implemented using the `DataChangedListener` appears in the https://www.codenameone.com/manual/components.html#Advanced-search-code[Toolbar section].
// HTML_ONLY_END
////
//PDF_ONLY
@@ -210,7 +210,7 @@ public class CustomToolbar extends Toolbar implements ScrollListener {
----
// HTML_ONLY_START
-NOTE: There is a better way of implementing this exact effect using title animations https://www.codenameone.com/manual/components.html#title-animations-section[illustrated here].
+NOTE: A better way of implementing this exact effect uses title animations https://www.codenameone.com/manual/components.html#title-animations-section[illustrated here].
// HTML_ONLY_END
////
//PDF_ONLY
@@ -237,7 +237,7 @@ for the component class but not a important event for general user code. it's re
==== 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 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.
+Component instances now publish lifecycle hooks that fire when they become initialized on a form and when they're 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
@@ -302,6 +302,7 @@ Each of those has advantages and disadvantages, specifically:
|Block current functionality
|Yes, just avoid super
+// vale-skip: Microsoft.Adverbs: "Partially" is the cell value for an event-consume scope, a precise classification, not authorial padding.
|Partially (event consume)
|No
|===
diff --git a/docs/developer-guide/Home.asciidoc b/docs/developer-guide/Home.asciidoc
index 4e1b731766..d0cc1cadbe 100644
--- a/docs/developer-guide/Home.asciidoc
+++ b/docs/developer-guide/Home.asciidoc
@@ -10,7 +10,7 @@ Welcome to the Codename One Developer Guide. This manual brings together tutoria
* *Deployment & Distribution* – Instructions for packaging, signing, and publishing to the major app stores alongside troubleshooting tips.
* *Appendices & Reference* – Supplemental material including glossary entries, configuration tables, and advanced topics for power users.
-Each section is organized to surface conceptual explanations first, followed by practical examples and deeper dives. Cross references within the guide link related topics so you can quickly pivot between beginner-friendly introductions and advanced techniques.
+Each section is organized to surface conceptual explanations first, followed by practical examples and deeper dives. Cross references within the guide link related topics so you can pivot between beginner-friendly introductions and advanced techniques.
== Recommended Resources
diff --git a/docs/developer-guide/Index.asciidoc b/docs/developer-guide/Index.asciidoc
index d10d6cf507..616f798900 100644
--- a/docs/developer-guide/Index.asciidoc
+++ b/docs/developer-guide/Index.asciidoc
@@ -37,7 +37,7 @@ IMPORTANT: Codename One doesn't send source code to the build cloud, compiled by
==== Why build servers
-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.
+The build servers can 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.
@@ -74,7 +74,7 @@ For desktop builds Codename One uses `javapackager`, since both Macs and Windows
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.
+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're 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.
@@ -134,7 +134,7 @@ In 2012 Shai and Chen formed Codename One as they left Oracle. The project has t
=== 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.
+Before you proceed, this section explains 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.
You can skip this section if you feel you're familiar enough with the core problems/issues in mobile app development.
@@ -154,13 +154,13 @@ Low resolution images on high PPI devices will look either small or pixelated. H
.How the Same Image Looks in Different Devices
image::img/image-sizes-different-dpis.png[How the Same Image Looks in Different Devices]
-The exact same image will look different on each device, sometimes to a comical effect. One of the solutions for this problem is multi-images. All OS’s support the ability to define different images for various densities. I will discuss multi-images later in Chapter 2.
+The exact same image will look different on each device, sometimes to a comical effect. One of the solutions for this problem is multi-images. All OS’s support the ability to define different images for various densities. Chapter 2 discusses multi-images later in detail.
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.
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.
+Chapter 2 discusses the layout managers in depth, 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
@@ -258,7 +258,7 @@ Before you get to the code there are few important things you need to understand
* *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 minimal starting point
+* *Theme* - Codename One ships with various built-in themes; for simplicity recommend `Native` as it's a minimal starting point
.Picking a Package Name
****
@@ -266,9 +266,9 @@ Apple, Google and Microsoft identify applications based on their package names.
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. 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:
+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`. Follow these 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 need
+* *Lower Case* - some OS's are case sensitive and handling a mistake in case is painful. The Java convention is lower case; stick to that
* *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,7 +289,7 @@ 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 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**.
+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 **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.
@@ -298,7 +298,7 @@ Emulators make sense for developers who want to build OS level services for exam
==== 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:
+After clicking finish in the new project wizard you've a `HelloWorld` project with a few default settings. The following sections break the class down to small pieces and explain each piece starting with the enclosing class:
[source,java,title='HelloWorld Class']
----
@@ -316,7 +316,7 @@ public class HelloWorld { // <1>
<3> Every app has a theme, it determines how everything within the application looks for example: colors, fonts etc.
-Next let's discuss the first lifecycle method `init(Object)`. I discuss the lifecycle in depth in the <>.
+Next consider the first lifecycle method `init(Object)`. The <> discusses the lifecycle in depth.
[source,java,title='HelloWorld init(Object)']
----
@@ -388,7 +388,7 @@ 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%]
-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 appear within this short snippet that this chapter addresses later 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
****
@@ -443,7 +443,7 @@ public void destroy() { // <4>
<4> `destroy()` is a special case. Under normal circumstances you shouldn't write code in `destroy()`. `stop()` should work for most cases
-that's it. Hopefully you've a general sense of the code. it's time to run on the device.
+that's it. You should now have a general sense of the code. it's time to run on the device.
==== Building and deploying on devices
@@ -488,7 +488,7 @@ image::img/android-certificate-generator.png[]
.Your certificate will generate into the file `Keychain.ks` in your home directory
TIP: Make sure to back that up and the password as losing these can have dire consequences
-.Should I Use a Different Certificate for Each App?
+.Should You 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 you often just use one.
@@ -499,12 +499,12 @@ The drawback of this approach occurs when you're building an app for someone els
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.
-However, first let's start with the good news:
+First, 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.
+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. One important caveat applies: You need an iOS Developer Account and Apple charges a 99USD Annual fee for that.
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
@@ -537,14 +537,14 @@ If you've more than one project you should use the same iOS P12 certificate file
One important aspect of provisioning on iOS is the device list in the provisioning step. Apple allows you to install the app on 100 devices during development. This blocks developers from skipping the App Store altogether. it's important you list the correct UDID for the device in the list otherwise install will fail.
-WARNING: There are many apps and tools that offer the UDID of the device, they aren't necessarily reliable and might give a fake number!
+WARNING: Many apps and tools offer the UDID of the device, but they aren't necessarily reliable and might give a fake number!
.Get the UDID of a Device
image::img/get-device-udid.png[Get the UDID of a Device]
TIP: You can right click the UDID and select #copy# to copy it
-The simplest and most reliable process for getting a UDID is through iTunes. I have used other approaches in the past that worked but this approach is guaranteed.
+The simplest and most reliable process for getting a UDID is through iTunes. Other approaches have worked in the past but this approach is guaranteed.
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
diff --git a/docs/developer-guide/Maven-Appendix-Control-Center.adoc b/docs/developer-guide/Maven-Appendix-Control-Center.adoc
index eb1fa7db2b..c8bf64b4e9 100644
--- a/docs/developer-guide/Maven-Appendix-Control-Center.adoc
+++ b/docs/developer-guide/Maven-Appendix-Control-Center.adoc
@@ -31,7 +31,7 @@ image::img/netbeans-open-control-center.png[]
==== 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.
+Press the image:img/eclipse-run-as-button.png[] button, and select the "_Your project_ Settings" option (where _Your project_ is the name of your project). For example:
image:img/eclipse-open-settings.png[]
diff --git a/docs/developer-guide/Maven-Appendix-Rich-Properties.adoc b/docs/developer-guide/Maven-Appendix-Rich-Properties.adoc
index e79af564e4..d708e523bb 100644
--- a/docs/developer-guide/Maven-Appendix-Rich-Properties.adoc
+++ b/docs/developer-guide/Maven-Appendix-Rich-Properties.adoc
@@ -2,7 +2,7 @@
[appendix]
== 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.
+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 accommodate properties whose values are "rich" and lengthy.
"Rich" properties use the following syntax.
@@ -44,7 +44,7 @@ favoriteColor=Brown
----
-Many of the properties of this file are regular properties. There are two rich properties: `bio` and `xmldata`.
+Many of the properties of this file are regular properties. Two rich properties exist: `bio` and `xmldata`.
diff --git a/docs/developer-guide/Maven-Creating-CN1Libs.adoc b/docs/developer-guide/Maven-Creating-CN1Libs.adoc
index f4cd28a393..aaa48d5b1d 100644
--- a/docs/developer-guide/Maven-Creating-CN1Libs.adoc
+++ b/docs/developer-guide/Maven-Creating-CN1Libs.adoc
@@ -17,7 +17,7 @@ You may be wondering why the .cn1lib format is even necessary. Why not just dist
. *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.
+. *Codename One libraries can include CSS files and build hints* which will be appended to the build hints of application projects when they're built.
// 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.
@@ -156,7 +156,7 @@ This will create a new libary project for you at the location you specified.
==== Project structure
-Let's take a look at the project that was created. it's a multi-module Maven project with the following modules:
+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 your cross-platform code and CSS, and build hint configuration. This module is in the "common" directory of the main project.
@@ -174,7 +174,7 @@ tests::
An application project for writing unit tests against your library. This module is in the "tests" directory of the main project.
-===== IntelliJ IDEA IDEA
+===== IntelliJ IDEA
The project inspector will look like:
@@ -192,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, 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.
+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. 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:
@@ -202,19 +202,20 @@ With the common subproject open, the project inspector will look like:
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.
+In this screenshot, "Source Packages" and "Other Sources/css" are expanded to highlight where your Java source files and CSS source files will be located.
The project inspector hides a few important files, but, so here is a screenshot of the File inspector for the common project:
+// vale-skip: Microsoft.FirstPerson: "my" is part of the literal image filename `netbeans-my-first-library-...`, not author voice.
image::img/netbeans-my-first-library-file-inspector-common.png[]
-===== Eclipse IDE IDE
+===== Eclipse IDE
The package explorer will look like:
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.
+In this screenshot, the _common/src/main/css_ and _common/src/main/java_ directories are expanded since this is where most of your module source will go.
===== Command line
@@ -353,7 +354,7 @@ To build the library, run the "install" goal on the root module as follows:
mvn install
----
-===== IntelliJ IDEA IDEA
+===== IntelliJ IDEA
Press the "build" image:img/intellij-build-icon.png[] button on the toolbar.
@@ -367,7 +368,7 @@ IMPORTANT: You must build the root module and not one of the submodules.
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
+===== Eclipse IDE
Right click on the "root" module in the project explorer and select _Run as_ > _Maven Install_
@@ -384,7 +385,7 @@ That being said, you may still want to distribute your library as a.cn1lib file
==== Editing Java Code
-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.
+To get acquainted with your project, 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:
@@ -403,7 +404,7 @@ Now build the library again. (See <>).
==== Using the library in an 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.
+Now that you've built your library and added a Java class, 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.
@@ -443,7 +444,7 @@ In this case you would add the following XML snippet to the `` sec
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):
+Now try it out. Try adding the following code to your application project's main class (or anywhere in the application project, for that matter):
[source,java]
----
diff --git a/docs/developer-guide/Maven-Getting-Started.adoc b/docs/developer-guide/Maven-Getting-Started.adoc
index 753a7d39b1..480410f09f 100644
--- a/docs/developer-guide/Maven-Getting-Started.adoc
+++ b/docs/developer-guide/Maven-Getting-Started.adoc
@@ -17,7 +17,7 @@ mvn -v
Both should report Java 11 or newer. The Codename One Maven plugin checks the
runtime JDK version when you invoke `mvn cn1:run` or `mvn cn1:debug` and fails
-fast with a friendly message if it is older than 11. Build-only goals (such as
+fast with a friendly message if it's older than 11. Build-only goals (such as
`-Pexecutable-jar`) still work on older JDKs.
[#creating-app-project]
@@ -35,7 +35,7 @@ The starter projects are based on the <>.
[TIP]
====
-The following tutorials provide step-by-step instructions for getting started with bare-bones app templates. Those tutorials are a better starting place for Codename One development than this manual, as they are written in tutorial form.
+The following tutorials provide step-by-step instructions for getting started with bare-bones app templates. Those tutorials are a better starting place for Codename One development than this manual, as they're written in tutorial form.
**Java Getting Started Tutorial:**
@@ -224,9 +224,9 @@ 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'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.
+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.
@@ -263,18 +263,18 @@ 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` to add the `` snippet to
-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.
+Suppose 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 your snippet into?
+**Question:** Which pom.xml file should you paste the snippet into?
**Answer:** common/pom.xml (almost always).
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.
-So, when adding dependencies into your app, you'll almost always place them inside the pom.xml file for the "common" module.
+When adding dependencies into your app, you'll therefore almost always place them inside the pom.xml file for the "common" module.
TIP: You can add dependencies without needing to change XML configuration files using the Control Center. See <>.
@@ -299,9 +299,9 @@ These modules don't use Maven for their dependencies (Android may deserve a smal
****
[#maven-dependency-example]
-==== Example: adding Google maps dependency via Maven central
+==== Example: Adding Google maps dependency via Maven central
-Let's add the GoogleMaps library to your app as a maven dependency.
+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:
@@ -352,7 +352,7 @@ If the compliance check fails (that is, the app uses unsupported APIs), the buil
[#managing-addons-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 <>.
+As mentioned 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 <>.
From the dashboard, select "Advanced Settings" > "Extensions" in the navigation menu on the left as shown below:
@@ -362,7 +362,7 @@ This will bring up a list of available Codename One extensions as shown below:
image::img/control-center-extensions.png[Control center extensions page]
-As an example, let's install the "Google Maps" library.
+As an example, install the "Google Maps" library.
Type in "Maps" into the search box, and it should narrow the options down to three libraries as shown below:
diff --git a/docs/developer-guide/Miscellaneous-Features.asciidoc b/docs/developer-guide/Miscellaneous-Features.asciidoc
index afef866c87..b0ead2449c 100644
--- a/docs/developer-guide/Miscellaneous-Features.asciidoc
+++ b/docs/developer-guide/Miscellaneous-Features.asciidoc
@@ -379,7 +379,7 @@ This applies to the layout managers (except for group layout) and most component
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., 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.
@@ -416,7 +416,7 @@ Legacy Ant projects have a different directory structure. They have no equivalen
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`. In the `NSCameraUsageDescription` case, you might include the build hint:
@@ -469,7 +469,7 @@ On Android the build generates locale-qualified drawable resources at every dens
No code changes are required—Android's resource framework switches icons when the locale changes.
-When you supply a region-qualified icon (such as `cn1_icon_ar_AE.png`) without a matching language-only variant, the build also emits the *default* (non-localized) icon into `drawable-/` and the matching `mipmap-*-/` directories. This barrier is required because Android's resource resolver (API 24+) walks every child of the parent locale when it can't find an exact or parent-language match, and would otherwise pick `ar-rAE` for, say, an `ar-PK` device. The barrier short-circuits that lookup so only devices whose region matches the supplied variant receive the localized icon. If you also ship a language-only file (for example `cn1_icon_ar.png`) it is used as the barrier instead, so you keep full control of the fallback icon for Arabic speakers outside AE.
+When you supply a region-qualified icon (such as `cn1_icon_ar_AE.png`) without a matching language-only variant, the build also emits the *default* (non-localized) icon into `drawable-/` and the matching `mipmap-*-/` directories. This barrier is required because Android's resource resolver (API 24+) walks every child of the parent locale when it can't find an exact or parent-language match, and would otherwise pick `ar-rAE` for, say, an `ar-PK` device. The barrier short-circuits that lookup so only devices whose region matches the supplied variant receive the localized icon. If you also ship a language-only file (for example `cn1_icon_ar.png`) it's used as the barrier instead, so you keep full control of the fallback icon for Arabic speakers outside AE.
===== iOS behaviour
@@ -504,6 +504,7 @@ Location position = LocationManager.getLocationManager().getCurrentLocationSync(
IMPORTANT: In order for location to work on iOS you *MUST* define the build hint `ios.locationUsageDescription` and describe why your application needs access to location. Otherwise you won't get location updates!
+// vale-skip: Microsoft.Adverbs: "repeatedly" is the precise frequency contrast with "once" here, not authorial padding.
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 and might not be right for some cases.
@@ -532,7 +533,7 @@ Because of the nature of background location the API is non-trivial. It starts w
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 about 10 seconds and the app could get killed if it exceeds that time slice.
+Notice that you *shouldn't* 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, 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.
@@ -1001,7 +1002,7 @@ hi.getToolbar().addCommandToRightBar("", icon, (ev) -> {
hi.add(BorderLayout.CENTER, iv);
----
-TIP: There is no need for a screenshot as it will look identical to the capture image screenshot above
+TIP: No need for a screenshot as it will look identical to the capture image screenshot above
The last value is the type of content picked which can be one of:
`Display.GALLERY_ALL`, `Display.GALLERY_VIDEO` or `Display.GALLERY_IMAGE`.
@@ -1172,7 +1173,7 @@ if(!fb.isUserLoggedIn()){
----
IMPORTANT: All these values are from the web version of the app! +
-They are used in the simulator and on "unsupported"
+They're used in the simulator and on "unsupported"
platforms as a fallback. Android and iOS will use the
native login
@@ -1200,7 +1201,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 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're 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.
@@ -1458,7 +1459,7 @@ Codename One has two basic ways to create new components:
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.
+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're all `Container` subclasses.
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`.
@@ -1670,11 +1671,11 @@ The real value is for 3rd party cn1lib authors. For example: Google Maps or Pars
=== Easy 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.
+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. A typical scenario: you need to convert some code to work on a separate thread but you still want the ability to communicate and transfer data from that 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?
+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` that let arbitrary code run on the EDT. Why not offer that to any random thread?
-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...
+That's why `EasyThread` exists: it takes some concepts of Codename 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:
@@ -1699,7 +1700,7 @@ e.run((success) -> success.onSuccess(doThisOnTheThread()), (myResult) -> onEDTGo
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:
+These asynchronous calls make things a bit painful to wade through, so the API wraps them in a simplified synchronous version:
[source,java]
----
@@ -1747,9 +1748,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. 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?
+You will notice you excluded the jars which are stored under lib and you exclude the Codename One source zip. However, cn1libs aren't excluded... 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. Git isn't good with binaries but cn1libs make sense. In another project that did have a cn1lib I did this:
+It's a fair question. Git isn't good with binaries but committing cn1libs can still make sense. In another project that did have a cn1lib the approach was this:
----
diff --git a/docs/developer-guide/Monetization.asciidoc b/docs/developer-guide/Monetization.asciidoc
index 39b96ac0ee..b124570b5e 100644
--- a/docs/developer-guide/Monetization.asciidoc
+++ b/docs/developer-guide/Monetization.asciidoc
@@ -15,7 +15,7 @@ To enable this for Android, define `android.googleAdUnitId=ca-app-pub-8610616152
.Google Play ads
image::img/google-play-ads.png[Google Play ads,scaledwidth=20%]
-There is a special ad unit ID for test ads. If you specify `ca-app-pub-3940256099942544/6300978111`, you get test ads during development. This matters because you aren't allowed to click your own ads. When it's time for a production release, replace it with the real value you generated in AdMob.
+A special ad unit ID exists for test ads. If you specify `ca-app-pub-3940256099942544/6300978111`, you get test ads during development. This matters because you aren't allowed to click your own ads. When it's time for a production release, replace it with the real value you generated in AdMob.
=== In-app purchase
@@ -27,10 +27,11 @@ in-app purchase support centers around the set of SKUs you want to sell. Each pr
==== Types of products
-There are 4 classifications for products:
+Four classifications for products exist:
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.
+// vale-skip: Microsoft.Auto: "auto-renewed" matches the App Store / Play Store wording for the renewal action; rewriting it as "autorenewed" diverges from Apple and Google's documented terminology.
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.
@@ -131,7 +132,7 @@ In the simulator, clicking on the "Buy World" button will bring up a prompt to a
.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's know that I already own it.
+Now if you try to buy the product again, it pops up the dialog to let you know that you already own it.
.In App buy already owned
image::img/iap-demo3.png[In App buy already owned,scaledwidth=20%]
@@ -199,11 +200,14 @@ NOTE: When you set up the products in the iTunes store you will need to mark the
As you discussed before, there are two types of subscriptions:
. Non-renewable
+// vale-skip: Microsoft.Auto: "Auto-renewable" matches Apple's documented subscription type.
. 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.
+// vale-skip: Microsoft.Auto: "Auto-renewable" matches Apple's documented subscription type.
+Non-renewable subscriptions are the same as consumable products, except that they're 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. 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.
+// vale-skip: proselint.Annotations: "NOTE" is the asciidoc admonition prefix here, not a TODO marker.
+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 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.
@@ -283,9 +287,9 @@ public static final String[] PRODUCTS = {
};
----
-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.
+Notice that you create two separate SKUs for the 1 month and 1 year subscription. **Each subscription period must have its own SKU**. The example uses 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 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.
+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're 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're 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. 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.
@@ -325,7 +329,7 @@ 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. 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.
+These methods are designed to be asynchronous since real-world apps will always be connecting to some sort of network service. 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, your `fetchReceipts()` method will look like:
@@ -431,7 +435,7 @@ NOTE: The iTunes store and Play store have no knowledge of your subscription dur
===== 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. 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.
+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 you may resynchronize at other strategic times if you suspect that the information may have changed.
The following methods can be used for synchronization:
@@ -605,25 +609,32 @@ image::img/in-app-purchase-subscription-toastbar-success.png[Upon successful buy
==== 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 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're 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're 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 subscribed to a subscription, and when the subscription expires.
+// vale-skip: Microsoft.Auto: "Auto-Renewable" is Apple's documented product type and is reproduced verbatim here.
==== Auto-Renewable subscriptions
+// vale-skip: Microsoft.Auto: "Auto-renewable" matches Apple/Google's documented subscription type.
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.
+// vale-skip: Microsoft.Auto: "auto-renewable" matches Apple's documented terminology for the subscription type.
. 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
+// vale-skip: Microsoft.Auto: "Auto-Renewable" matches Apple's documented product type used as a heading anchor.
+==== Auto-Renewable vs Non-Renewable: Choosing between them
+// vale-skip: Microsoft.Auto: Multiple references to Apple/Google's "auto-renewable" subscription terminology.
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.
+// vale-skip: Microsoft.Auto: References Apple/Google's documented "auto-renewable" subscription type and a third-party blog URL containing the same term.
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.
+// vale-skip: Microsoft.Auto: "auto-renewable" matches Apple's documented subscription type.
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
@@ -696,7 +707,7 @@ If you're not sure how to create a data source, see your https://www.codenameone
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.
+1. Build and Run the server project in Netbeans. You may need to tell it which application server you wish to run it on. This example runs 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:
@@ -723,7 +734,7 @@ 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's why your expiry date may be different than expected.
-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:
+Just to verify that the receipt was inserted, you should check the contents of your "RECEIPTS" table in your database. In Netbeans, you 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%]
@@ -740,7 +751,7 @@ If you delete the receipt from your database, then press the "Synchronize Receip
====== 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.
+Don't pretend that everything worked 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.
@@ -748,11 +759,11 @@ Common problems would be that the URL you have set in the client app for `endpoi
==== Looking at the source of the app
-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.
+Now that you've set up and built the app, take a look at the source code so you can see how it all works.
===== Client side
-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 example uses 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 your ReceiptStore is as follows:
@@ -842,7 +853,7 @@ private RESTfulWebServiceClient createRESTClient(String url) {
===== Server-Side
-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:
+On the server-side, your REST controller is a standard JAX-RS REST interface. The Netbeans web service wizard generated it and then it was modified to suit the purposes here. The methods of the `ReceiptsFacadeREST` class for the REST API are shown here:
[source,java]
----
@@ -886,7 +897,7 @@ The magic happens inside that `validateAndSaveReceipt()` method, which You'll co
====== Notifications
-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:
+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, the example below sets it up to run once per minute:
[source,java]
----
@@ -920,11 +931,12 @@ public void validateSubscriptionsCron() {
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.
+// vale-skip: Microsoft.Auto: "auto-renewing" matches Google Play's documented subscription terminology.
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
-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.
+For this tutorial, the example uses a purpose-built 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,7 +961,7 @@ 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 get these values soon.
+The next section walks through the steps to get these values.
==== The `validateAndSaveReceipt()` method
@@ -1091,21 +1103,21 @@ private Receipts[] validateAndSaveReceipt(Receipts receipt) {
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 your 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. 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
===== Creating the app in Google play
-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]. This section doesn't describe the process, but plenty of information around the internet covers 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
-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, this example uses 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
@@ -1113,7 +1125,7 @@ IMPORTANT: You can't buy in-app products from your app using your publisher acco
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]. But, 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]; the documentation is dense and can be 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 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.
@@ -1151,7 +1163,7 @@ IMPORTANT: Since you will be adding products as "Subscriptions" in the pay store
.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 selected for this example.
.Add product to google
image::img/iap3-add-product-google.png[Add product to google]
@@ -1193,8 +1205,8 @@ image::img/iap3-credentials-dropdown.png[Credentials dropdown,scaledwidth=20%]
+
.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, 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.
+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, avoid 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 autogenerate an account ID for you.
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 like the following:
@@ -1247,7 +1259,7 @@ image::img/iap3-link-to-api.png[Link to API,scaledwidth=20%]
+
.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 this tutorial, I just want to pull out all 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, the goal is to remove all potential road blocks.
+
.New User
image::img/iap3-new-user.png[New User,scaledwidth=30%]
@@ -1285,6 +1297,7 @@ The process to add products in iTunes connect is outlined https://developer.appl
. **iapdemo.noads.month.auto** - The 1 month subscription.
. **iapdemo.noads.3month.auto** - The 3 month subscription.
+// vale-skip: Microsoft.Auto: "auto-renewable" matches Apple's documented subscription type.
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
@@ -1322,4 +1335,4 @@ Change this to `false`.
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 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. In practice they renew only 4 or 5 times before they're allowed to lapse by Apple.
diff --git a/docs/developer-guide/Native-Themes.asciidoc b/docs/developer-guide/Native-Themes.asciidoc
index af4a5786e9..b0709c404f 100644
--- a/docs/developer-guide/Native-Themes.asciidoc
+++ b/docs/developer-guide/Native-Themes.asciidoc
@@ -2,7 +2,7 @@
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
-hint. Both are fully overridable - their colour palettes, type
+hint. Both are fully overridable - their color palettes, type
ramps, and state-specific styles are all reachable from your own
`theme.css` and from runtime API.
@@ -71,11 +71,11 @@ appearance:
system setting (useful for theme-preview screens or accessibility
toggles).
-=== Material 3 colour palette (Android)
+=== Material 3 color palette (Android)
The Android Material 3 theme is built around the Material Design 3
-"baseline" palette. Each colour is referenced from one or more
-UIIDs; if you change a colour the change ripples to every UIID that
+"baseline" palette. Each color is referenced from one or more
+UIIDs; if you change a color the change ripples to every UIID that
inherits from it.
[cols="1,1,1,2", options="header"]
@@ -86,7 +86,7 @@ inherits from it.
|`#6750a4`
|`#d0bcff`
|Default `Button` fill, `BackCommand` / `TitleCommand` text,
-`SelectedTab` text colour.
+`SelectedTab` text color.
|on-primary
|`#ffffff`
@@ -103,7 +103,7 @@ inherits from it.
|`#fef7ff`
|`#141218`
|`Form`, `ContentPane`, `Toolbar`, `TitleArea`, `List`, `Tabs`,
-`SideNavigationPanel`. The "page" colour.
+`SideNavigationPanel`. The "page" color.
|on-surface
|`#1d1b20`
@@ -148,7 +148,7 @@ inherits from it.
|All `.disabled` overrides.
|===
-To rebrand the app, override the colour at the role level rather
+To rebrand the app, override the color at the role level rather
than touching every UIID. The example below layers a teal palette
on top from your app's own `theme.css`; if you want the same
rebrand to apply at *runtime* (for in-app accent toggles, branded
@@ -175,7 +175,7 @@ BackCommand { color: #00796b; }
TitleCommand { color: #00796b; }
----
-=== iOS modern colour palette
+=== iOS modern color palette
The iOS modern theme follows Apple's system palette.
@@ -208,7 +208,7 @@ The iOS modern theme follows Apple's system palette.
|`#f2f2f7`
|`#1c1c1e`
|Form / ContentPane (the iOS "grouped" form bg). `Dialog` /
-`Tabs` use this colour at reduced opacity for the liquid-glass
+`Tabs` use this color at reduced opacity for the liquid-glass
look.
|surface-tertiary
@@ -242,8 +242,8 @@ look.
|`Switch.selected`, `OnOffSwitch.selected`.
|===
-To layer a brand colour on top, override `RaisedButton` and
-accent-driven UIIDs the same way as Android above. The colour names
+To layer a brand color on top, override `RaisedButton` and
+accent-driven UIIDs the same way as Android above. The color names
match Apple's `UIColor.systemBlue` etc. so you can mirror the SF
Symbols semantics if you want.
@@ -292,7 +292,7 @@ recompile.
|`@accent-on-color`
|`#ffffff`
|(same)
-|Text colour painted on top of the accent fill.
+|Text color painted on top of the accent fill.
|===
.Android Material 3 (`native-themes/android-material/theme.css`)
@@ -361,8 +361,8 @@ runtime overrides:
}
----
-Bindings that reference a constant you did not override stay at
-their baked-in default, so a partial override (e.g. just
+Bindings that reference a constant you didn't override stay at
+their baked-in default, so a partial override (for example, just
`--accent-color`) is fine.
==== Runtime override
@@ -397,22 +397,22 @@ suite exercises this path against both native themes, light + dark.
==== When the override path doesn't apply
The binding mechanism handles every accent UIID the shipped themes
-expose. For other widgets - or when you want to override a colour
-the native CSS hard-codes (e.g. the iOS `success` green on
+expose. For other widgets - or when you want to override a color
+the native CSS hard-codes (for example, the iOS `success` green on
`Switch.selected`) - either:
* Layer a per-UIID redeclaration in your app's `theme.css` (see
- `Customising in your own theme` below). Right when you want to
+ `Customizing in your own theme` below). Right when you want to
tweak a UIID the binding vocabulary doesn't already cover.
* Or pass the specific UIID/state key directly into
`UIManager.addThemeProps` (`Switch.sel#bgColor` etc.). Runtime,
- bypasses the binding system. Right when you need a one-off colour
+ bypasses the binding system. Right when you need a one-off color
tweak you can't anchor in CSS.
=== Platform-specific UIIDs
A handful of UIIDs exist only because one platform draws them
-differently from the other. They are still safe to override; you
+differently from the other. They're still safe to override; you
just want to know which platform's screen the override is going to
land on.
@@ -420,7 +420,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 top of the
+ capture this reads as a colored 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
@@ -505,7 +505,7 @@ mode. Both modern themes set this; user themes that want dark
mode also need to set it.
|===
-=== Customising in your own theme
+=== Customizing in your own theme
Your app's `theme.css` inherits from the installed native theme:
diff --git a/docs/developer-guide/Push-Notifications.asciidoc b/docs/developer-guide/Push-Notifications.asciidoc
index ea61b3b173..0efa878b6b 100644
--- a/docs/developer-guide/Push-Notifications.asciidoc
+++ b/docs/developer-guide/Push-Notifications.asciidoc
@@ -13,7 +13,7 @@ Push notifications provide a way to inform users that something of interest has
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.
-Also to messages that the user *sees*, a push notification can contain non-visual information that's *silently* sent to your app.
+In addition to messages that the user *sees*, a push notification can contain non-visual information sent to your app without any UI surfacing.
=== Implementing push support
@@ -61,7 +61,7 @@ There will be more steps required to deploy to each platform (for example: iOS r
=== The push lifecycle
-Let's take minute to go over the three callbacks in your application.
+Take a minute to go over the three callbacks in your application.
==== Registration
@@ -73,7 +73,7 @@ If the registration failed for some reason, the `pushRegistrationError()` callba
Notice that all this happens seamlessly behind the scenes when your app loads. You don't need to start any of this workflow.
-NOTE: On iOS, the system permission prompt that asks the user to allow notifications is shown the first time you call `Display.getInstance().registerPush()` (or schedule a `LocalNotification`). It is no longer shown automatically when the app launches, so you can display your own rationale screen first and call `registerPush()` only after the user has agreed. This matches the Android flow. Apps that need the legacy launch-time prompt for backward compatibility can set the `ios.notificationPermissionAtLaunch=true` build hint.
+NOTE: On iOS, the system permission prompt that asks the user to allow notifications is shown the first time you call `Display.getInstance().registerPush()` (or schedule a `LocalNotification`). It's no longer shown automatically when the app launches, so you can display your own rationale screen first and call `registerPush()` only after the user has agreed. This matches the Android flow. Apps that need the legacy launch-time prompt for backward compatibility can set the `ios.notificationPermissionAtLaunch=true` build hint.
==== Sending a push notification
@@ -144,7 +144,7 @@ The "Push Type" drop-down allows you to select the "type" of the push message. T
.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'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."
+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:
@@ -161,7 +161,7 @@ You should see the following output in your console:
Received push message: Hello World
----
-This experiment simulated a push notification while the app is running in the foreground. Now let's simulate the case where the app isn't running, or running in the background. You can simulate this by pausing the app. Return to the Codename One simulator window, and select "Pause App" from the "Simulate" menu as shown below.
+This experiment simulated a push notification while the app is running in the foreground. Now simulate the case where the app isn't running, or running in the background. You can simulate this by pausing the app. Return to the Codename One simulator window, and select "Pause App" from the "Simulate" menu as shown below.
.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%]
@@ -504,7 +504,7 @@ 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 it 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%]
@@ -530,7 +530,7 @@ All you need to do here is press the "Download google-services.json" file, then
IMPORTANT: Firebase console directs you to copy the google-services.json file into the "app" directory of your project. Ignore this direction as it applies for Android studio projects. For Codename One, this file goes into the #native/android# directory of your project.
-There is one last piece of information that you need so that you can *send* push notifications to your app: The `FCM_SERVER_API_KEY` value.
+One last piece of information remains for you to *send* push notifications to your app: The `FCM_SERVER_API_KEY` value.
Go to your project dashboard in Firebase console. Then click the "Settings" menu (the "Gear" icon next to "Project Overview" in the upper left):
@@ -554,7 +554,7 @@ Push on iOS is much harder to handle than the Android version, but you simplifie
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!
+They're *completely different certificates* that have nothing to do with the build process!
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:
@@ -796,4 +796,5 @@ many things to notice in the responses above:
IMPORTANT: APNS (Apple's push service) returns uppercase key results. This means that code for managing the keys in your database must be case insensitive
+// vale-skip: Microsoft.Adverbs: "fail silently" is the precise technical contrast with returning an error, not authorial padding.
TIP: Apple doesn't always send back a result for a device being inactive and might fail silently
diff --git a/docs/developer-guide/Skin-Designer.asciidoc b/docs/developer-guide/Skin-Designer.asciidoc
index 65c61a6b69..b9613bf9a5 100644
--- a/docs/developer-guide/Skin-Designer.asciidoc
+++ b/docs/developer-guide/Skin-Designer.asciidoc
@@ -46,7 +46,7 @@ the grid tells you when you're looking at a clamped subset.
image::img/skin-designer/skin-designer-stage-2-source.png[Source picker with three large cards: Pick a shape, Upload an image, Blank rectangle,role="text-center"]
-There are three ways to seed the skin's body image:
+Three ways exist to seed the skin's body image:
[[skin-designer-source-shape]]
*Pick a shape* — generates the device frame procedurally from a small
@@ -232,7 +232,7 @@ monospaceFontFamily=SF Mono
NOTE: The wizard intentionally does *not* write `smallFontSize`,
`mediumFontSize`, or `largeFontSize`. When those are absent the
-simulator auto-derives them from `pixelMilliRatio` (`med = round(2.6
+simulator automatically derives them from `pixelMilliRatio` (`med = round(2.6
* ppmm)`, `sm = 2 * ppmm`, `la = 3.3 * ppmm`), which is what you
want on high-PPI screens. Writing iOS-style point values (12 / 15 /
22) into the file used to make text render at sub-millimeter sizes
diff --git a/docs/developer-guide/The-Components-Of-Codename-One.asciidoc b/docs/developer-guide/The-Components-Of-Codename-One.asciidoc
index 778834c3ef..fdd453b30a 100644
--- a/docs/developer-guide/The-Components-Of-Codename-One.asciidoc
+++ b/docs/developer-guide/The-Components-Of-Codename-One.asciidoc
@@ -42,7 +42,7 @@ Lead components work by assigning one component as the "leader." That leader det
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].
-The lead component also handles events from a single source. Clicking another component in the hierarchy sends the event to the leading `Button`, which can make action events behave unexpectedly. that's why `getActualComponent()` exists.
+The lead component also handles events from a single source. Clicking another component in the hierarchy sends the event to the leading `Button`, which can make action events route to a different target than the apparent click target. That's why `getActualComponent()` exists.
// HTML_ONLY_START
You can learn more about lead components in https://www.codenameone.com/manual/misc-features.html#lead-component-section[here].
@@ -120,7 +120,7 @@ form.add(BorderLayout.SOUTH, bottomBar);
form.show();
----
-Safe-area padding is applied when the container does **not** have a scrollable parent. For scrollable content, you can assume the user can scroll the component into view instead.
+Safe-area padding is applied when the container **doesn't** have a scrollable parent. For scrollable content, you can assume the user can scroll the component into view instead.
Most layouts never need to know where the safe area begins, but if you draw manually (for example, inside `paint()` or on the glass pane) you can query it directly:
@@ -361,7 +361,7 @@ Dialogs in Codename One can be modal or modeless, the former blocks the calling
For example, there is another definition to those terms: A modal dialog blocks access to the rest of the UI while 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
+In that sense, all dialogs in Codename One are modal; they block the parent form since they're 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 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.
@@ -392,7 +392,7 @@ This will show the dialog on the right hand side of the screen, which is pretty
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.
+To make popup behaviour feel natural on touch devices you can call `setDisposeWhenPointerOutOfBounds(true)` so the dialog automatically 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.
By default the dialog is placed on the form's layered pane, but you can switch between the global layered pane and form-specific layered pane using `setFormMode(boolean)`. Setting form mode to `true` keeps the dialog coupled with the showing form even when the global layered pane is used elsewhere in your app.
@@ -603,7 +603,7 @@ This will adapt the icon for the action on the keys.
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:
+To unify the behavior you slightly customized the iOS keyboard as such:
.Next virtual keyboard with toolbar
image::img/components-textfield-vkb-next.png[Next virtual keyboard with toolbar,scaledwidth=20%]
@@ -785,7 +785,7 @@ https://www.codenameone.com/javadoc/com/codename1/ui/Button.html[Button] is a su
Button adds to the mix some more states such as a pressed `UIID` state and pressed icon.
-NOTE: There are more icon states in `Button` such as rollover and disabled icon.
+NOTE: `Button` supports more icon states 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`.
@@ -849,7 +849,7 @@ image::img/raised-flat-buttons.png[Raised and flat button in simulator,scaledwid
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:
+In this case set the background color to purple and the foreground to white:
.Purple raised button
image::img/raised-flat-buttons-purple.png[Purple raised button,scaledwidth=40%]
@@ -985,10 +985,10 @@ IMPORTANT: By default, `ComponentGroup` does *nothing*. You need to explicitly a
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`
-3. If I have three elements within a component group they will have the UIID's `GroupElementFirst`, `GroupElement`, `GroupElementLast`
-4. If I have four elements within a component group they will have the UIID's `GroupElementFirst`, `GroupElement`, `GroupElement`, `GroupElementLast`
+1. With one element in a component group it will have the UIID: `GroupElementOnly`
+2. With two elements in a component group they will have the UIID's `GroupElementFirst`, `GroupElementLast`
+3. With three elements in a component group they will have the UIID's `GroupElementFirst`, `GroupElement`, `GroupElementLast`
+4. With four elements in a component group they will have the UIID's `GroupElementFirst`, `GroupElement`, `GroupElement`, `GroupElementLast`
This allows you to define special styles for the edges.
@@ -1321,6 +1321,7 @@ hi.add(BorderLayout.CENTER, ic);
=== List, MultiList, renderers & models
+// vale-skip: Microsoft.HeadingPunctuation: "vs." is the abbreviation for "versus"; the period is part of the abbreviation, not a heading-end terminator.
==== InfiniteContainer/InfiniteScrollAdapter vs. List/ContainerList
Your recommendation is to always go with `Container`, `InfiniteContainer` or `InfiniteScrollAdapter`.
@@ -1343,7 +1344,7 @@ You deprecated `ContainerList` which performs badly and has some inherent comple
`MultiList` is a reasonable version of `List` that's far easier to use without most of the pains related to renderer configuration.
-There are cases where using `List` or `MultiList` is justified, they are rarer than usual hence your recommendation.
+Some cases justify using `List` or `MultiList`, but they're rarer than usual, hence the recommendation.
==== MVC in lists
@@ -1359,14 +1360,14 @@ The list renderer is like a rubber stamp that knows how to draw an object from t
TIP: Think of the render as a translation layer that takes the "data" from the model and translates it to a visual representation.
-This is all generic, but a bit too much for most, doing a list "properly" requires some understanding. The main source of confusion for developers is the stateless nature of the list and the transfer of state to the model (for example: a checkbox list needs to listen to action events on the list and update the model, in order for the renderer to display that state). Once you understand that it’s easy.
+This is all generic, but a bit too much for most, doing a list correctly requires some understanding. The main source of confusion for developers is the stateless nature of the list and the transfer of state to the model (for example: a checkbox list needs to listen to action events on the list and update the model, in order for the renderer to display that state). Once you understand that it’s easy.
==== Understanding MVC
-Let's recap, what is MVC:
+A quick recap of what MVC is:
- #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.
+- #View# - The view draws the content of the model. it's a "dumb" layer that has no notion of what's 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.
.Typical MVC Diagram footnote:[Image by RegisFrey - Own work, Public Domain, https://commons.wikimedia.org/w/index.php?curid=10298177]
@@ -1450,7 +1451,7 @@ private Map createListEntry(String name, String date) {
}
----
-There is one major piece missing here and that's the cover images for the books. A simple approach would be to place the image objects into the entries using the "icon" property as such:
+One major piece is missing here: the cover images for the books. A simple approach would be to place the image objects into the entries using the "icon" property as such:
[source,java]
----
@@ -1581,7 +1582,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's try to make it a bit more useful.
+Now make it a bit more useful.
[source,java]
----
@@ -1671,7 +1672,7 @@ In this renderer you want to render a `Contact` object to the Screen, you build
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 change the focus `Component` to have an icon.
+For example, you can change the focus `Component` to have an icon.
[source,java]
----
@@ -1715,7 +1716,7 @@ 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 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're 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.
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).
@@ -1811,7 +1812,7 @@ Because of the rendering architecture of a `List` its pretty hard to calculate t
As you might guess this triggers a performance penalty that's paid with every reflow of the UI. The solution is to use `setRenderingPrototype`.
-`setRenderingPrototype` accepts a "fake" value that represents a reasonably large amount of data and it will be used to calculate the preferred size. For example: for a multiList that should render 2 lines of text with 20 characters and a 5mm square icon I can do something like this:
+`setRenderingPrototype` accepts a "fake" value that represents a reasonably large amount of data and it will be used to calculate the preferred size. For example: for a multiList that should render 2 lines of text with 20 characters and a 5mm square icon you can do something like this:
[source,java]
----
@@ -2720,7 +2721,7 @@ image::img/components-toolbar-search-ongoing.png[Search field after typing a cou
==== Search mode
-While you can implement search manually using the built-in search offers a simpler and more uniform UI.
+While you can implement search manually, using the built-in search offers a simpler and uniform UI.
.built-in toolbar search functionality
image::img/toolbar-search-mode.jpg[built-in toolbar search functionality,scaledwidth=40%]
@@ -2911,7 +2912,7 @@ java.lang.UnsatisfiedLinkError: .../codenameone-cef-*-linux64.zip-extracted/lib/
(required by .../lib/linux64/libjcef.so)
----
-The native CEF library (`libjcef.so`) is loaded with a sibling `libjawt.so` shipped inside the CEF archive. That copy of `libjawt.so` expects symbols (`SUNWprivate_1.1`) provided by the JDK's own `libjawt.so` and the matching `libjvm.so`. When the JVM running the Simulator does not expose those symbols at load time -- typically because the dynamic linker does not have the JDK's native library directories on its search path -- the load fails before any browser is created. This is purely a Simulator/desktop concern; the same code runs unchanged on Android and iOS.
+The native CEF library (`libjcef.so`) is loaded with a sibling `libjawt.so` shipped inside the CEF archive. That copy of `libjawt.so` expects symbols (`SUNWprivate_1.1`) provided by the JDK's own `libjawt.so` and the matching `libjvm.so`. When the JVM running the Simulator doesn't expose those symbols at load time -- typically because the dynamic linker doesn't have the JDK's native library directories on its search path -- the load fails before any browser is created. This is purely a Simulator/desktop concern; the same code runs unchanged on Android and iOS.
The fix is to launch the IDE (and therefore the Simulator JVM) with `JAVA_HOME` and `LD_LIBRARY_PATH` pointing at a supported JDK. For example, on Linux Mint / Ubuntu wrap the IDE startup script:
@@ -2923,11 +2924,11 @@ export LD_LIBRARY_PATH=$JAVA_HOME/lib:$JAVA_HOME/lib/server${LD_LIBRARY_PATH:+:$
exec /path/to/your/ide "$@"
----
-After relaunching the IDE through this wrapper, `BrowserComponent` instantiation succeeds in the Simulator. Any JDK from 11 through 25 that is supported for running the Simulator works -- the important part is that `LD_LIBRARY_PATH` resolves to that JDK's `lib` and `lib/server` directories so the bundled `libjcef.so` finds a compatible `libjawt`/`libjvm` pair.
+After relaunching the IDE through this wrapper, `BrowserComponent` instantiation succeeds in the Simulator. Any JDK from 11 through 25 that's supported for running the Simulator works -- the important part is that `LD_LIBRARY_PATH` resolves to that JDK's `lib` and `lib/server` directories so the bundled `libjcef.so` finds a compatible `libjawt`/`libjvm` pair.
-TIP: If `BrowserComponent` instantiation fails on Linux, log the environment with `CN.getProperty("java.home", "")`, `CN.getProperty("JAVA_HOME", "")` and `CN.getProperty("LD_LIBRARY_PATH", "")`. An `` value for `LD_LIBRARY_PATH` while the IDE was launched from a desktop shortcut is the typical fingerprint of this problem -- desktop launchers do not source your shell profile.
+TIP: If `BrowserComponent` instantiation fails on Linux, log the environment with `CN.getProperty("java.home", "")`, `CN.getProperty("JAVA_HOME", "")` and `CN.getProperty("LD_LIBRARY_PATH", "")`. An `` value for `LD_LIBRARY_PATH` while the IDE was launched from a desktop shortcut is the typical fingerprint of this problem -- desktop launchers don't source your shell profile.
-NOTE: This issue does not affect packaged native applications, only the desktop Simulator running on Linux.
+NOTE: This issue doesn't affect packaged native applications, only the desktop Simulator running on Linux.
==== BrowserComponent hierarchy
@@ -3009,7 +3010,7 @@ 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.
-===== So what was wrong with the old API
+===== 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 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:
@@ -3063,7 +3064,7 @@ Log.p("The result was "+res.Int());
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.
+IMPORTANT: When using the `andWait()` variant, it's critical 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
@@ -3294,7 +3295,7 @@ This code effectively navigates to the Codename One home page by fetching the DO
==== 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.
+PhoneGap was one of the first web app packager tools in the market: a tool that's effectively a browser component within a native wrapper coupled with native access APIs, and Cordova is the open source extension of this popular project.
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.
@@ -3313,7 +3314,7 @@ This is discussed further in the https://www.codenameone.com/blog/phonegap-cordo
The https://www.codenameone.com/javadoc/com/codename1/ui/AutoCompleteTextField.html[AutoCompleteTextField] allows you to write text into a text field and select a completion entry from the list in a similar way to a search engine.
-This is easy to incorporate into your code, replace your usage of https://www.codenameone.com/javadoc/com/codename1/ui/TextField.html[TextField] with `AutoCompleteTextField` and define the data that the autocomplete should work from. There is a default implementation that accepts a `String` array or a https://www.codenameone.com/javadoc/com/codename1/ui/list/ListModel.html[ListModel] for completion strings, this can work well for a "small" set of thousands (or tens of thousands) of entries.
+This is easy to incorporate into your code, replace your usage of https://www.codenameone.com/javadoc/com/codename1/ui/TextField.html[TextField] with `AutoCompleteTextField` and define the data that the autocomplete should work from. A default implementation accepts a `String` array or a https://www.codenameone.com/javadoc/com/codename1/ui/list/ListModel.html[ListModel] for completion strings, this can work well for a "small" set of thousands (or tens of thousands) of entries.
For example: This is a trivial use case that can work well for smaller sample sizes:
@@ -3392,13 +3393,13 @@ image::img/dynamic-autocomplete.png[Autocomplete Text Field with a webservice,sc
==== Using images in AutoCompleteTextField
-One question I got a few times is "How do you customize the results of the auto complete field"?
+One question that comes up frequently 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 more data or format the date correctly?
-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 answer is actually pretty simple, you still need to work with Strings because autocomplete 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:
+The following source code presents an autocomplete text field with images in the completion popup and two lines for every entry:
[source,java]
----
@@ -3619,9 +3620,9 @@ 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 managed for me.
+This didn't mesh well with the GUI builder navigation logic and so it needed a rethink. The aim was to reuse GUI as much as possible while still enjoying the advantage of navigation being managed for you.
-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.
+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's 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 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."
@@ -4191,7 +4192,7 @@ 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 last 3 arguments seem weird but they also make sense once you understand them, they're:
- The least position of the split - 1/4 of available space
- The default position of the split - middle of the screen
diff --git a/docs/developer-guide/The-EDT---Event-Dispatch-Thread.asciidoc b/docs/developer-guide/The-EDT---Event-Dispatch-Thread.asciidoc
index ae9879a0e4..188932b3a2 100644
--- a/docs/developer-guide/The-EDT---Event-Dispatch-Thread.asciidoc
+++ b/docs/developer-guide/The-EDT---Event-Dispatch-Thread.asciidoc
@@ -1,9 +1,9 @@
== The EDT - Event Dispatch Thread
[[edt-section]]
-=== What Is The EDT
+=== What's The EDT
-Codename One allows developers to create as many threads as they want; however in order to interact with the Codename One user interface components a developer must use the EDT. The EDT stands for "Event Dispatch Thread" but it handles a lot more than "events".
+Codename One allows developers to create as many threads as they want; however to interact with the Codename One user interface components a developer must use the EDT. The EDT stands for "Event Dispatch Thread" but it handles a lot more than "events."
The EDT is the main thread of Codename One, by using one thread Codename One can avoid complex synchronization code and focus on simple functionality that assumes one thread.
@@ -21,7 +21,7 @@ while(codenameOneRunning) {
}
----
-Normally, every call you receive from Codename One will occur on the EDT. E.g. every event, calls to paint(), lifecycle calls (start etc.) should all occur on the EDT.
+Normally, every call you receive from Codename One will occur on the EDT. For example, every event, calls to paint(), and lifecycle calls (start etc.) should all occur on the EDT.
This is pretty powerful, however it means that as long as your code is processing nothing else can happen in Codename One!
@@ -32,7 +32,7 @@ The solution is pretty simple, if you need to perform something that requires in
Codename One’s networking code automatically spawns its own network thread (see the https://www.codenameone.com/javadoc/com/codename1/io/NetworkManager.html[NetworkManager]). However, this also poses a problem...
Codename One assumes all modifications to the UI
-are performed on the EDT but if we spawned a separate thread. How do we force our modifications back into the EDT?
+are performed on the EDT. If you spawn a separate thread, how do you force its modifications back into the EDT?
Codename One includes helper methods in the https://www.codenameone.com/javadoc/com/codename1/ui/Display.html[Display] class to help in these situations: `isEDT()`, `callSerially(Runnable)`, `callSeriallyOnIdle(Runnable)`, and `callSeriallyAndWait(Runnable)` (with an optional timeout overload).
@@ -91,7 +91,7 @@ If the work you're posting back to the EDT is expensive and can wait until the U
==== callSerially On The EDT
-One of the misunderstood topics is why would we ever want to invoke `callSerially` when you're still on the EDT. This is best explained by example. Say you've a button that has quite a bit of functionality tied to its events e.g.:
+One of the misunderstood topics is why you'd ever want to invoke `callSerially` when you're still on the EDT. This is best explained by example. Say you've a button that has quite a bit of functionality tied to its events e.g.:
1. A user added an action listener to show a Dialog.
@@ -99,31 +99,31 @@ One of the misunderstood topics is why would we ever want to invoke `callSeriall
3. The button repaints a release animation as its being released.
-However, this might cause a problem if the first event that we handle (the dialog) might cause an issue to the
-following events. E.g. a dialog will block the EDT (using `invokeAndBlock`), events will keep happening but since
-the event you're in "already happened" the button repaint and the framework logging won't occur. This might
-also happen if we show a form which might trigger logic that relies on the current form still being present.
+However, this might cause a problem if the first event being handled (the dialog) causes an issue with the
+following events. For example, a dialog will block the EDT (using `invokeAndBlock`), events will keep happening but since
+the event you're in "already happened" the button repaint and the framework logging won't occur. The same problem
+also occurs if you show a form which might trigger logic that relies on the current form still being present.
One of the solutions to this problem is to wrap the action listeners body with a `callSerially`. In this case the `callSerially`
will postpone the event to the next cycle (loop) of the EDT and let the other events in the chain complete. Notice
that you shouldn't use this normally since it includes an overhead and complicates application flow, however when
-you run into issues in event processing we suggest trying this to see if its the cause.
+you run into issues in event processing this is worth trying to see if its the cause.
IMPORTANT: You should never invoke callSeriallyAndWait on the EDT since this would effectively mean sleeping on the
-EDT. We made that method throw an exception if its invoked from the EDT.
+EDT. That method throws an exception if its invoked from the EDT.
-If you need to run logic on the EDT but must ensure nothing inside it blocks, the `Display` class also provides `invokeWithoutBlocking()` and `invokeWithoutBlockingWithResultSync()`. These helpers temporarily disable `invokeAndBlock()` for the duration of the runnable. Any nested call to `invokeAndBlock()` while blocking is disabled results in a `BlockingDisallowedException`. Framework code that wraps user callbacks can use these methods to guard against accidental nested blocking that would otherwise deadlock or stall the UI, yet still safely obtain a return value when needed via `invokeWithoutBlockingWithResultSync()`.
+If you need to run logic on the EDT but must ensure nothing inside it blocks, the `Display` class also provides `invokeWithoutBlocking()` and `invokeWithoutBlockingWithResultSync()`. These helpers temporarily disable `invokeAndBlock()` during the runnable. Any nested call to `invokeAndBlock()` while blocking is disabled results in a `BlockingDisallowedException`. Framework code that wraps user callbacks can use these methods to guard against accidental nested blocking that would otherwise deadlock or stall the UI, yet still obtain a return value when needed via `invokeWithoutBlockingWithResultSync()`.
=== Debugging EDT Violations
-There are two types of EDT violations:
+Two types of EDT violations exist:
1. Blocking the EDT thread so the UI performance is considerably slower.
2. Invoking UI code on a separate thread
Codename One provides a tool to help you detect some of these violations some caveats may apply though…
-It’s an imperfect tool. It might fire "false positives" meaning it might detect a violation for perfectly legal code and it might miss some illegal calls. However, it's a valuable tool in the process of detecting hard to track bugs that are sometimes reproducible on the devices (due to race condition behavior).
+It’s an imperfect tool. It might fire "false positives" meaning it might detect a violation for legal code and it might miss some illegal calls. However, it's a valuable tool in the process of detecting hard to track bugs that are sometimes reproducible on the devices (due to race condition behavior).
To activate this tool select the Debug EDT menu option in the simulator and pick the level of output you wish to receive:
@@ -138,7 +138,7 @@ Full output will include stack traces to the area in the code that's suspected i
Invoke and block is the exact opposite of `callSeriallyAndWait()`, it blocks the EDT and opens a separate thread for the runnable call. This functionality is inspired by the http://foxtrot.sourceforge.net/[Foxtrot] API, which is
a remarkably powerful tool most Swing developers don't know about.
-This is best explained by an example. When we write typical code in Java we like that code is in sequence as such:
+This is best explained by an example. Typical Java code reads as a single sequence as such:
[source,java]
----
@@ -160,8 +160,8 @@ new Thread() {
doOperationC();
----
-Unfortunately, this means that operation C will happen in parallel to operation B which might be a problem... +
-E.g. instead of using operation names lets use a more "real world" example:
+This means that operation C will happen in parallel to operation B which might be a problem... +
+For example, instead of using operation names lets use a more "real world" example:
[source,java]
----
@@ -186,7 +186,7 @@ new Thread() {
}).start();
----
-But `updateUIWithContentOfFile` should be executed on the EDT and not on a random thread. So the right way to do this would be something like this:
+However, `updateUIWithContentOfFile` should be executed on the EDT and not on a random thread. The right way to do this would therefore be something like this:
[source,java]
----
@@ -203,7 +203,7 @@ new Thread() {
}).start();
----
-This is perfectly legal and would work reasonably well, however it gets complicated as we add more and more features that need to be chained serially after all these are 3 methods!
+This is legal and would work reasonably well, however it gets complicated as you add more and more features that need to be chained serially after all these are 3 methods!
Invoke and block solves this in a unique way you can get almost the exact same behavior by using this:
@@ -232,7 +232,7 @@ Invoke and block effectively blocks the current EDT in a legal way. It spawns a
All events and EDT behavior still work while `invokeAndBlock` is running, this is because `invokeAndBlock()` keeps calling the main thread loop internally.
IMPORTANT: Notice that `invokeAndBlock` comes at a slight performance penalty. Also notice that nesting `invokeAndBlock` calls (or over using them) isn't recommended. +
-However, they are convenient when working with multiple threads/UI.
+However, they're convenient when working with multiple threads/UI.
Even if you never call `invokeAndBlock` directly you're probably using it indirectly in API's such as https://www.codenameone.com/javadoc/com/codename1/ui/Dialog.html[Dialog] that show a dialog while blocking the current thread e.g.:
@@ -277,7 +277,7 @@ void invokeAndBlock(Runnable r) {
}
----
-So the EDT is effectively "blocked" but we "redo it" within the `invokeAndBlock` method...
+The EDT is effectively "blocked" but `invokeAndBlock` "redoes" it from within...
As you can see this is a simple approach for thread programming in UI, you don't need to block your flow and
track the UI thread. You can program in a way that seems sequential (top to bottom) but uses multi-threading
diff --git a/docs/developer-guide/Theme-Basics.asciidoc b/docs/developer-guide/Theme-Basics.asciidoc
index b52d061230..682e7367ba 100644
--- a/docs/developer-guide/Theme-Basics.asciidoc
+++ b/docs/developer-guide/Theme-Basics.asciidoc
@@ -208,13 +208,13 @@ Notice that https://www.codenameone.com/javadoc/com/codename1/ui/Container.html[
.Alignment of the text within some component types
image::img/theme-entry-align.png[Alignment of the text within some component types]
-WARNING: Aligning text components to anything other than the default alignment might be a problem if they are editable. The native editing capabilities might collide with the alignment behavior.
+WARNING: Aligning text components to anything other than the default alignment might be a problem if they're editable. The native editing capabilities might collide with the alignment behavior.
NOTE: Bidi/RtL layout reverses the alignment value so left becomes right and visa versa
==== 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:
+Padding and margin are concepts derived from the CSS box model. They're slightly different in Codename One, where the border spacing is part of the padding, but other than that they're pretty similar:
.Padding and Margin/Box Model
image::img/padding.png[Padding and Margin,scaledwidth=50%]
@@ -308,7 +308,7 @@ The UI for the 9-piece border you created above looks like <()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 (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.
+// vale-skip: proselint.Annotations: 'XXX' is a placeholder for the actual method name inside a verbatim quote of a TeaVM error log message.
2. "Method XXX wasn't found"
+
-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.
+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:
@@ -174,11 +176,11 @@ For example, If your application is hosted at http://example.com/myapp/index.htm
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.
-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.
+A solution exists. 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, 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.
@@ -186,7 +188,7 @@ The easiest way to set up a proxy is to use the Codename One *https://github.com
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
three ways to configure your application to use your proxy.
@@ -437,7 +439,7 @@ or
===== Embedding variables in URLs
-In some cases the URL for a library may depend on the values of some build hints in the project. For example, in the Google Maps cn1lib, the API key must be appended to the URL for the API as a GET parameter. For example, `https://maps.googleapis.com/maps/api/js?v=3.exp&key=SOME_API_KEY`, but the developer of the library doesn't want to put his own API key in the manifest file for the library. It would be better for the API key to be supplied by the developer of the actual app that uses the library and not the library itself.
+Sometimes the URL of a library may depend on the values of some build hints in the project. For example, in the Google Maps cn1lib, the API key must be appended to the URL of the API as a GET parameter. For example, `https://maps.googleapis.com/maps/api/js?v=3.exp&key=SOME_API_KEY`, but the developer of the library doesn't want to put his own API key in the manifest file for the library. It would be better for the API key to be supplied by the developer of the actual app that uses the library and not the library itself.
The solution for this is to add a *variable* into the URL as follows:
@@ -501,6 +503,7 @@ Note: If the port number isn't specified or if it's the scheme's default port (l
| The User-agent string identifying the browser, version etc..
| `browser.language`
+// vale-skip: Microsoft.We: "US" appears inside the BCP 47 locale code "en-US", not a first-person pronoun.
| The language code that the browser is currently set to. (for example, en-US)
| `browser.name`
@@ -524,11 +527,11 @@ For example: `MacIntel`, `Win32`, `FreeBSD i386`, "WebTV OS"
[id="native_theme", reftext="Changing the Native Theme"]
=== Changing the native theme
-Since a web application could potentially be run on any platform, it isn't feasible to bundle all possible themes into the application (at least it wouldn't be efficient for most use cases). By default you've bundled the iOS7 theme for javascript applications. This means that the app will look like iOS7 on all devices: desktop, iOS, Android, WinPhone, etc...
+Since a web application could be run on any platform, bundling all possible themes into the application isn't feasible (at least it wouldn't be efficient for most use cases). By default you've bundled the iOS7 theme for javascript applications. This means that the app will look like iOS7 on all devices: desktop, iOS, Android, WinPhone, etc...
-You can override this behavior dynamically by setting the `javascript.native.theme` https://www.codenameone.com/javadoc/com/codename1/ui/Display.html[Display] property to a theme that you've included in your app. All of the native themes are available on GitHub, so you can easily copy these into your application. The best place to add the theme is in your `native/javascript` directory - so that they won't be included for other platforms.
+You can override this behavior dynamically by setting the `javascript.native.theme` https://www.codenameone.com/javadoc/com/codename1/ui/Display.html[Display] property to a theme that you've included in your app. All the native themes are available on GitHub, so you can copy these into your application. The best place to add the theme is in your `native/javascript` directory, which excludes them from other platforms.
-==== Example: using Android theme on Android
+==== Example: Using Android theme on Android
NOTE: As of Codename One 6.0, apps will automatically use the Android theme when run on an Android device, so this example isn't necessary. However the technique of changing the native theme at runtime is still applicable.
@@ -555,7 +558,7 @@ You can explicitly enable or disable this behaviour by setting the "platformHint
NOTE: Some browsers don't allow you to specify the message that's displayed in this dialog. In those browsers, this property can be viewed as boolean: A null value will result in no prompt being shown, and a non-null value will result in a prompt being shown.
-==== Example: toggling the BeforeUnload prompt On/Off
+==== Example: Toggling the BeforeUnload prompt On/Off
[source,java]
----
@@ -576,7 +579,7 @@ f.show();
[id="pwa_deployment", reftext="Deploying as a Progressive Web App"]
=== Deploying as a progressive web app
-Out of the box, your app is ready to be deployed as a progressive web app (PWA). That means that users can access the app directly in their browser, but once the browser determines that the user is frequenting the app, it will "politely" prompt the user to install the app on their home screen. Once installed on the home screen, the app will behave like a native app. It will continue to work while offline, and if the user launches the app, it will open without the browser's navigation bar. If you were to install the native and PWA versions of your app side by side, you would be hard pressed to find the difference - especially on newer devices.
+Out of the box, your app is ready to be deployed as a progressive web app (PWA). That means that users can access the app directly in their browser, but once the browser determines that the user is frequenting the app, it will prompt the user to install the app on their home screen. Once installed on the home screen, the app will behave like a native app. It will continue to work while offline, and if the user launches the app, it will open without the browser's navigation bar. If you were to install the native and PWA versions of your app side by side, you would be hard pressed to find the difference - especially on newer devices.
Below is a screenshot from Chrome for Android where the browser is prompting the user to add the app to their home screen.
@@ -603,7 +606,7 @@ Build hints of the form `javascript.manifest.XXX` will be injected into the app
javascript.manifest.description=An app for doing cool stuff
----
-You can find a full list of available manifest keys https://developer.mozilla.org/en-US/docs/Web/Manifest[here]. The build server will automatically generate all of the icons so you don't need to worry about those. The "name" and "short_name" properties will default to the app's display name, but they can be overridden via the `javascript.manifest.name` and `javascript.manifest.short_name` build hints respectively.
+You can find a full list of available manifest keys https://developer.mozilla.org/en-US/docs/Web/Manifest[here]. The build server will automatically generate all the icons so you don't need to worry about those. The "name" and "short_name" properties will default to the app's display name, but they can be overridden via the `javascript.manifest.name` and `javascript.manifest.short_name` build hints respectively.
NOTE: The `javascript.manifest.related_applications` build hint expects a JSON formatted list, like in the raw manifest file.
@@ -636,15 +639,16 @@ The app will also appear in their "Shelf" which you can always access at `chrome
.Chrome App Launcher
image::img/javascript-pwa-chrome-app-launcher.png[Chrome App Launcher,scaledwidth=20%]
-NOTE: The Chrome App Launcher lists apps installed both via the Chrome Web Store and via the "Add to Shelf" feature that we discuss here. The features we describe in this article are orthogonal to the Chrome Web Store and won't be affected by its closure.
+// vale-skip: proselint.Annotations: "NOTE" is the asciidoc admonition prefix here, not a TODO marker.
+NOTE: The Chrome App Launcher lists apps installed both via the Chrome Web Store and via the "Add to Shelf" feature discussed here. The features described in this article are orthogonal to the Chrome Web Store and won't be affected by its closure.
=== Playing media and opening links
-People don't like it when the browser automatically starts playing sounds, or opening links without their permission. For this reason, modern browsers generally restrict your ability to programmatically do these things, unless they are in response to a user action, like a mouse click.
+People don't like it when the browser automatically starts playing sounds, or opening links without their permission. For this reason, modern browsers restrict your ability to programmatically do these things, unless they're in response to a user action, like a mouse click.
If your app needs to play media (for example, `Media.play()`), or open a link (for example, `Display.execute("...")`) without the user actually interacting physically (for example, key press or pointer press), then it will display a popup dialog confirming that the user actually wants to perform this action.
-In some cases this dialog may affect the utility of the app. For example, suppose you want to play a video in response to a voice command. Having to press an "OK" button after the command, may be annoying. For such cases, you can use the `platformHint.javascript.backsideHooksInterval` property to *poll* for media play requests on an authorized event.
+Sometimes this dialog may affect the utility of the app. For example, suppose you want to play a video in response to a voice command. Having to press an "OK" button after the command, may be annoying. For such cases, you can use the `platformHint.javascript.backsideHooksInterval` property to *poll* for media play requests on an authorized event.
For example:
diff --git a/docs/developer-guide/Working-With-iOS.asciidoc b/docs/developer-guide/Working-With-iOS.asciidoc
index d2041cf435..91b4b46535 100644
--- a/docs/developer-guide/Working-With-iOS.asciidoc
+++ b/docs/developer-guide/Working-With-iOS.asciidoc
@@ -22,7 +22,7 @@ If you've access to a Mac, connect the device, open Xcode, and use the device ex
[[section-ios-launch-screen]]
=== Launch screen storyboard best practices
-Launch screen storyboards are the default approach for Codename One iOS builds. Apple requires a storyboard-based launch experience for modern devices, so the legacy screenshot generator was removed in favour of a single adaptive layout. You can still opt back into the old behaviour by setting the `ios.generateSplashScreens=true` build hint, but it's best to use a storyboard unless your use case can't be expressed with Auto Layout.
+Launch screen storyboards are the default approach for Codename One iOS builds. Apple requires a storyboard-based launch experience for modern devices, so the legacy screenshot generator was removed in favor of a single adaptive layout. You can still opt back into the old behavior by setting the `ios.generateSplashScreens=true` build hint, but it's best to use a storyboard unless your use case can't be expressed with Auto Layout.
==== Key files
@@ -75,7 +75,7 @@ When iterating locally with a Mac, open the generated Xcode project and run it o
=== Local notifications on iOS and Android
-Local notifications are like push notifications, except that they are initiated locally by the app, rather than remotely. They are useful for communicating information to the user while the app is running in the background, since they manifest themselves as pop-up notifications on supported devices.
+Local notifications are like push notifications, except that they're initiated locally by the app, rather than remotely. They're useful for communicating information to the user while the app is running in the background, since they manifest themselves as pop-up notifications on supported devices.
TIP: To set the notification icon on Android 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
@@ -158,7 +158,7 @@ NOTE: `localNotificationReceived()` is called when the user responds to the noti
==== Canceling notifications
-Repeating notifications will continue until they are canceled by the app. You can cancel a single notification by calling:
+Repeating notifications will continue until they're canceled by the app. You can cancel a single notification by calling:
[source,java]
-----
@@ -177,11 +177,13 @@ NOTE: This is supported for pro users as part of the crash protection feature.
To take advantage of that capability use the build hint `ios.testFlight=true` and then submit the app to the store for
beta testing. Make sure to use a release build target.
-=== Choosing a colour space for the Metal renderer
+=== Choosing a color space for the Metal renderer
-When the Metal rendering backend is enabled with `ios.metal=true`, the `CAMetalLayer` is configured with the sRGB colour space by default. This matches the behaviour of the legacy OpenGL ES 2 backend on `CAEAGLLayer`: CG-rasterised images and gradients (which are tagged `DeviceRGB` in their `CGBitmapContext`) end up displayed with the same brightness on both backends.
+// vale-skip: Microsoft.Avoid: "rendering backend" is Apple's term of art for an alternate Metal/OpenGL rendering pipeline, not a back-end server.
+When the Metal rendering backend is enabled with `ios.metal=true`, the `CAMetalLayer` is configured with the sRGB color space by default. This matches the behavior of the legacy OpenGL ES 2 backend on `CAEAGLLayer`: CG-rasterized images and gradients (which are tagged `DeviceRGB` in their `CGBitmapContext`) end up displayed with the same brightness on both backends.
-For most apps the default is the right choice. Apps that need a different colour profile -- for example, wide-gamut artwork that should be displayed in Display P3, or a strictly device-RGB pipeline that matches a custom rendering toolchain -- can override the choice with the `ios.metal.colorSpace` build hint:
+// vale-skip: Microsoft.Adverbs: "strictly device-RGB" is a precise color-pipeline qualifier (excluding sRGB conversion), not a softener.
+For most apps the default is the right choice. Apps that need a different color profile -- for example, wide-gamut artwork that should be displayed in Display P3, or a strictly device-RGB pipeline that matches a custom rendering toolchain -- can override the choice with the `ios.metal.colorSpace` build hint:
[cols="1,3"]
|===
@@ -209,7 +211,8 @@ For most apps the default is the right choice. Apps that need a different colour
| Leaves `metalLayer.colorspace` unset, so Metal uses the system default for the device.
|===
-NOTE: The hint only takes effect when `ios.metal=true`. With the OpenGL ES 2 backend the layer is `CAEAGLLayer` and the colour space is fixed by the system.
+// vale-skip: Microsoft.Avoid: "OpenGL ES 2 backend" names the alternate rendering pipeline, not a server.
+NOTE: The hint only takes effect when `ios.metal=true`. With the OpenGL ES 2 backend the layer is `CAEAGLLayer` and the color space is fixed by the system.
=== Accessing insecure URL's
@@ -228,7 +231,7 @@ For example, it seems that Apple will reject your app if you include that and do
NOTE: CocoaPods remains fully supported, but it's no longer the only supported iOS dependency path. Swift Package Manager (SPM) is also supported. The current guidance is documented in this section.
-https://cocoapods.org/[CocoaPods] is a dependency manager for Swift and Objective-C Cocoa projects. It has over eighteen thousand libraries and can help you scale your projects elegantly. Cocoapods can be used in your Codename One project to include native iOS libraries without having to go through the hassle of bundling the actual library into your project. Rather than bundling `.h` and `.a` files in your ios/native directory, you can specify which "pods" your app uses through the `ios.pods` build hint. (There are other build hints also if you need more advanced features).
+https://cocoapods.org/[CocoaPods] is a dependency manager for Swift and Objective-C Cocoa projects. It has over eighteen thousand libraries and can help you scale your projects. Cocoapods can be used in your Codename One project to include native iOS libraries without having to go through the hassle of bundling the actual library into your project. Rather than bundling `.h` and `.a` files in your ios/native directory, you can specify which "pods" your app uses through the `ios.pods` build hint. (Other build hints also exist if you need more advanced features.)
**Examples**
@@ -264,7 +267,7 @@ ios.pods=AFNetworking ~> 3.0,GoogleMaps
`ios.pods.platform` : The least platform to target. Sometimes, Cocoapods require functionality that isn't in older version of iOS. For example, the GoogleMaps pod requires iOS 7.0 or higher, so you would need to add the `ios.pods.platform=7.0` build hint.
-`ios.pods.sources` : Some pods require that you specify a URL for the source of the pod spec. This may be optional if the spec is hosted in the central CocoaPods source (`https://github.com/CocoaPods/Specs.git`).
+`ios.pods.sources` : Some pods require that you specify a URL of the source of the pod spec. This may be optional if the spec is hosted in the central CocoaPods source (`https://github.com/CocoaPods/Specs.git`).
==== Converting PodFile to build hints
diff --git a/docs/developer-guide/Working-with-Mac-OS-X.asciidoc b/docs/developer-guide/Working-with-Mac-OS-X.asciidoc
index c2c1f97bc7..4179ec4bd8 100644
--- a/docs/developer-guide/Working-with-Mac-OS-X.asciidoc
+++ b/docs/developer-guide/Working-with-Mac-OS-X.asciidoc
@@ -16,10 +16,10 @@ Here you can provide your certificate(s) as a `.p12` file, and select a bundle t
==== Bundle Types
-There are three bundle types which dictate what the build server produces for you when you build your project as a Desktop Mac OS App.
+Three bundle types dictate what the build server produces for you when you build your project as a Desktop Mac OS App.
. **DMG** - Produces a `.DMG` disk image with your app. This is the preferred format for distributing your app outside of the Mac App Store. If you provide a Developer ID Application certificate (see "Understanding Certificates" below), the app will be signed so that users won't receive warnings about "Unidentified developer" when they install your app.
-. **Sandboxed DMG** - Same as **DMG** bundle type except that your app is set up to use the app sandbox. Generally this would be used to test an app that's being distributed via the App Store, since App Store apps *must* use the sandbox. If you select this bundle type, you're *required* to provide a Mac App Distribution Certificate, and you should additionally specify entitlements required for your app to function properly. For more information about the app sandbox, see https://developer.apple.com/app-sandboxing/[Apple's documentation on the subject].
+. **Sandboxed DMG** - Same as **DMG** bundle type except that your app is set up to use the app sandbox. This is typically used to test an app that's being distributed via the App Store, since App Store apps *must* use the sandbox. If you select this bundle type, you're *required* to provide a Mac App Distribution Certificate, and you should also specify entitlements required for your app to function correctly. For more information about the app sandbox, see https://developer.apple.com/app-sandboxing/[Apple's documentation on the subject].
. **Mac App Store Upload (PKG)** - Produces a `.PKG` file that you can upload to the Mac App Store. This requires that you provide *both* a Mac App Distribution certificate, and a Mac App Installer certificate (see "Understanding Certificates" below). Both of these certificates should be embedded into a single `.p12` file (See "Exporting Certificates as p12" below).
==== Understanding Mac Certificates
@@ -30,13 +30,13 @@ For the purposes of Mac application distribution, there are 3 types of certifica
. **Developer ID Application Certificate (Mac applications)**
+
-This type of certificate is used to sign an app to be distributed *outside* of the Mac App Store as a DMG image. This corresponds to the "DMG" bundle type in Codename one settings. You can easily identity this kind of certificate because its identity will be of the form "Developer ID Application: YOUR COMPANY NAME (SOMECODE)". E.g. Developer ID Application: Acme Widgets Corp. (XYSD5YF).
+This type of certificate is used to sign an app to be distributed *outside* of the Mac App Store as a DMG image. This corresponds to the "DMG" bundle type in Codename one settings. You can identify this kind of certificate because its identity will be of the form "Developer ID Application: YOUR COMPANY NAME (SOMECODE)." For example, Developer ID Application: Acme Widgets Corp. (XYSD5YF).
. **Mac App Distribution Certificate (Mac App Store)**
+
-This type of certificate is used to sign the.app bundle for an app that's to be distributed in the Mac App Store. This certificate is required for both the "Sandboxed DMG", and "Mac App Store Upload (PKG)" bundle types. You can easily identify this kind of certificate because its identity will be of the form "3rd Party Mac Developer Application: YOUR COMPANY NAME (SOMECODE)". E.g. 3rd Party Mac Developer Application: Acme Widgets Corp. (XYSD5YF).
+This type of certificate is used to sign the.app bundle for an app that's to be distributed in the Mac App Store. This certificate is required for both the "Sandboxed DMG," and "Mac App Store Upload (PKG)" bundle types. You can identify this kind of certificate because its identity will be of the form "3rd Party Mac Developer Application: YOUR COMPANY NAME (SOMECODE)." For example, 3rd Party Mac Developer Application: Acme Widgets Corp. (XYSD5YF).
. **Mac App Installer Certificate (Mac App Store)**
+
-This type of certificate is used to sign the.pkg installer for an app that's being submitted to the Mac App Store. This certificate is required for the "Mac App Store Upload (PKG)" bundle type. You can easily identify this kind of certificate because its identity will be of the form "3rd Party Mac Developer Installer: YOUR COMPANY NAME (SOMECODE)". E.g. 3rd Party Mac Developer Installer: Acme Widgets Corp. (XYXD5YF).
+This type of certificate is used to sign the.pkg installer for an app that's being submitted to the Mac App Store. This certificate is required for the "Mac App Store Upload (PKG)" bundle type. You can identify this kind of certificate because its identity will be of the form "3rd Party Mac Developer Installer: YOUR COMPANY NAME (SOMECODE)." For example, 3rd Party Mac Developer Installer: Acme Widgets Corp. (XYXD5YF).
==== Obtaining Certificates
@@ -53,7 +53,7 @@ The screenshot above shows an account that already has the three kinds of certif
. **Mac App Distribution** - Used for the Sandboxed DMG and Mac App Store Upload (PKG) bundle types.
. **Mac App Installer** - Used for the Mac App Store Upload (PKG) bundle type.
-If your account doesn't yet have a certificate of the required type, you should begin by pressing the "+" button in the upper right. This will bring you to a page asking "What type of Certificate do you need?". There are two options on this page that you will be interested in:
+If your account doesn't yet have a certificate of the required type, you should begin by pressing the "+" button in the upper right. This will bring you to a page asking "What type of Certificate do you need?" Two options on this page will interest you:
.Creating a new certificate
image::img/mac-developer-guide-what-type-of-certificate.png[Creating a new certificate,scaledwidth=30%]
@@ -73,7 +73,7 @@ After generating the certificates, you should download them to your Mac, and imp
NOTE: The following section requires access to a Mac, and assumes that you've already generated your 3 certificates
-Notice that Mac apps may require three different kinds of certificates, yet the settings page provides space for a single certificate (P12) file. This isn't a mistake. P12 files may contain more than one certificate, and you're expected to include all of the certificates that the build server may require inside a single P12. The build server will automatically extract the certificates it needs according to the bundle type.
+Notice that Mac apps may require three different kinds of certificates, yet the settings page provides space for a single certificate (P12) file. This isn't a mistake. P12 files may contain more than one certificate, and you're expected to include all the certificates that the build server may require inside a single P12. The build server will automatically extract the certificates it needs according to the bundle type.
When building the "DMG" bundle type, the build server will look for a "Developer ID Application Certificate" inside the P12. If one is found, it will be used to sign the app bundle.
@@ -90,20 +90,21 @@ You will then be prompted to select a location to save the `.p12` file, as well
==== Entitlements
-When distributing apps in the Mac App Store, or when using the "Sandboxed DMG" bundle type, your app is run inside a sandboxed environment, meaning that it doesn't have access to the outside world. It's provided its own "sandboxed" container for file system access, and it doesn't get any network access. If your app requires access to the "outside world", you need to request entitlements for that access. If you select a bundle type that uses the sandbox, you will be shown a list of all of the available entitlements from which you can "check" the ones that you wish to include.
+When distributing apps in the Mac App Store, or when using the "Sandboxed DMG" bundle type, your app is run inside a sandboxed environment, meaning that it doesn't have access to the outside world. It's provided its own "sandboxed" container for file system access, and it doesn't get any network access. If your app requires access to the "outside world," you need to request entitlements for that access. If you select a bundle type that uses the sandbox, you will be shown a list of all the available entitlements from which you can "check" the ones that you wish to include.
.App sandbox entitlements
image::img/mac-desktop-entitlements.png[App sandbox entitlements,scaledwidth=30%]
For more information about the app sandbox, and a full list of entitlements, see https://developer.apple.com/library/content/documentation/Miscellaneous/Reference/EntitlementKeyReference/Chapters/EnablingAppSandbox.html[Apple's documentation on the subject].
-TIP: You can specify entitlements using the build hint `desktop.mac.entitlement.XXXX` where XXX is the entitlement you wish to set. E.g. `desktop.mac.entitlement.com.apple.security.network.client=true`
+// vale-skip: proselint.Annotations: 'XXX' is a placeholder for the entitlement name inside a literal build-hint template, not a TODO marker.
+TIP: You can specify entitlements using the build hint `desktop.mac.entitlement.XXXX` where XXX is the entitlement you wish to set. For example, `desktop.mac.entitlement.com.apple.security.network.client=true`
==== Custom Info.plist Entries
You can add custom entries to the https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Introduction/Introduction.html[Info.plist file] using the following build hints:
desktop.mac.plist.KEYNAME::
-Injects the entry with key `KEYNAME` into the Info.plist file. E.g. `desktop.mac.plist.LSApplicationCategoryType=public.app-category.business`
+Injects the entry with key `KEYNAME` into the Info.plist file. For example, `desktop.mac.plist.LSApplicationCategoryType=public.app-category.business`
diff --git a/docs/developer-guide/appendix_goal_generate_app_project.adoc b/docs/developer-guide/appendix_goal_generate_app_project.adoc
index 92cf618395..24be8ae459 100644
--- a/docs/developer-guide/appendix_goal_generate_app_project.adoc
+++ b/docs/developer-guide/appendix_goal_generate_app_project.adoc
@@ -108,9 +108,9 @@ template.type=maven
====
----
-==== Sample: the bare-bones Kotlin app project
+==== Sample: The bare-bones Kotlin app project
-As a more complete example of a project template, see the https://github.com/shannah/cn1app-archetype-kotlin-template/blob/master/generate-app-project.rpf[generate-app-project.rpf] file in the https://github.com/shannah/cn1app-archetype-kotlin-template[bare-bones kotlin app template].
+As a fuller example of a project template, see the https://github.com/shannah/cn1app-archetype-kotlin-template/blob/master/generate-app-project.rpf[generate-app-project.rpf] file in the https://github.com/shannah/cn1app-archetype-kotlin-template[bare-bones kotlin app template].
This is the template that's used in https://www.codenameone.com/initializr[Codename One initializr] for the Bare-bones Kotlin project.
diff --git a/docs/developer-guide/appendix_goal_install_cn1lib.adoc b/docs/developer-guide/appendix_goal_install_cn1lib.adoc
index e1115c7bdd..7461ed8943 100644
--- a/docs/developer-guide/appendix_goal_install_cn1lib.adoc
+++ b/docs/developer-guide/appendix_goal_install_cn1lib.adoc
@@ -2,7 +2,7 @@
Installs a legacy cn1lib file as a dependency in this application project.
-See <> for more complete coverage of project dependencies.
+See <> for fuller coverage of project dependencies.
NOTE: Using this goal explicitly is a last resort. The best solution for installing add-ons into your project is <>.
diff --git a/docs/developer-guide/appendix_goal_update.adoc b/docs/developer-guide/appendix_goal_update.adoc
index a7d6ee38b5..fde4e70c78 100644
--- a/docs/developer-guide/appendix_goal_update.adoc
+++ b/docs/developer-guide/appendix_goal_update.adoc
@@ -15,6 +15,6 @@ mvn cn1:update
newVersion::
(Optional) The version to update to. This should be a version number available on Maven central. Will accept a value of "LATEST" to cause it to resolve to the latest version available on Maven central.
+
-If this parameter is omitted, then it will be implicitly set to `LATEST`, but it won't update the `cn1.version` or `cn1.plugin.version` properties if they are set to a SNAPSHOT version.
+If this parameter is omitted, then it will be implicitly set to `LATEST`, but it won't update the `cn1.version` or `cn1.plugin.version` properties if they're set to a SNAPSHOT version.
See <> for more information about updating Codename One.
diff --git a/docs/developer-guide/basics.asciidoc b/docs/developer-guide/basics.asciidoc
index 0533046cca..ad65137432 100644
--- a/docs/developer-guide/basics.asciidoc
+++ b/docs/developer-guide/basics.asciidoc
@@ -1,4 +1,4 @@
-== Basics: themes, styles, components, and layouts
+== Basics: Themes, styles, components, and layouts
Start with a brief overview of the core ideas in Codename One. This chapter revisits them in more detail as it goes on.
@@ -315,7 +315,7 @@ Sometimes the growing behavior in the X axis is undesired, for these cases you c
image::img/box-layout-x-no-grow.png[BoxLayout X_AXIS_NO_GROW,scaledwidth=20%]
.`FlowLayout` vs. `BoxLayout.X_AXIS`
-TIP: When applicable recommend `BoxLayout` over `FlowLayout` as it acts more consistently in all situations. Another advantage of `BoxLayout` is the fact that it grows and thus aligns nicely
+TIP: When applicable recommend `BoxLayout` over `FlowLayout` as it acts more consistently in all situations. Another advantage of `BoxLayout` is the fact that it grows and thus aligns the components in a consistent column or row
===== Border Layout
@@ -413,7 +413,7 @@ Grid also has an autoFit attribute that can be used to automatically calculate t
available space and preferred width. This is useful for working with UI's where the device orientation
might change.
-There is also a terse syntax for working with a grid that has two versions, one that uses the "auto fit" option and
+A terse syntax for working with a grid also exists in two versions, one that uses the "auto fit" option and
another that accepts the number of columns. Here's a sample of the terse syntax coupled with auto fit followed by screenshots
of the same code in two orientations:
@@ -585,11 +585,11 @@ As you can see from the code and samples above there is a lot going on under the
The `TextModeLayout` isn't a layout as much as it's a delegate. When running in the Android mode (which you refer to as the "`on top`" mode) the layout is almost an exact synonym of `TableLayout` and in fact delegates to an underlying `TableLayout`. In fact there is a `public final` table instance within the layout that you can refer to directly...
-There is one small difference between the `TextModeLayout` and the underlying `TableLayout` and that's your choice to default to align entries to `TOP` with this mode.
+One small difference exists between the `TextModeLayout` and the underlying `TableLayout`: your choice to default to align entries to `TOP` with this mode.
TIP: Aligning to TOP is important for error handling for `TextComponent` in Android otherwise the entries "`jump`"
-When working in the non-android environment you use a `BoxLayout` on the Y axis as the delegate. There is one thing you do here that's different from a default box layout: grouping. Grouping allows the labels to align by setting them to the same width, internally it invokes `Component.setSameWidth()`. Since text components hide the labels there is a special `group` method there that can be used. For example, this is implicit with the `TextModeLayout` which is pretty cool.
+When working in the non-android environment you use a `BoxLayout` on the Y axis as the delegate. One thing you do here differs from a default box layout: grouping. Grouping allows the labels to align by setting them to the same width, internally it invokes `Component.setSameWidth()`. Since text components hide the labels there is a special `group` method there that can be used. For example, this is implicit with the `TextModeLayout` which is pretty cool.
`TextModeLayout` was created specifically for the `TextComponent` and `InputComponent` so check out the section about them in the components chapter.
@@ -647,7 +647,7 @@ TIP: Codename One also includes a GlassPane that resides on top of the layered p
[[insets-and-reference-components]]
===== Insets and reference components
-As of Codename One 3.7, https://www.codenameone.com/javadoc/com/codename1/ui/layouts/LayeredLayout.html[LayeredLayout] supports https://www.codenameone.com/javadoc/com/codename1/ui/layouts/LayeredLayout.LayeredLayoutConstraint.Inset.html[insets] for its children. This effectively allows you to position child components precisely where you want them, relative to their container or siblings. This functionality forms the under-pinnings of the GUI Builder's <>.
+As of Codename One 3.7, https://www.codenameone.com/javadoc/com/codename1/ui/layouts/LayeredLayout.html[LayeredLayout] supports https://www.codenameone.com/javadoc/com/codename1/ui/layouts/LayeredLayout.LayeredLayoutConstraint.Inset.html[insets] for its children. This effectively allows you to position child components precisely where you want them, relative to their container or siblings. This functionality forms the under-pinnings of the GUI Builder's <>.
As an example, suppose you wanted to position a button in the lower right corner of its container. This can be achieved with https://www.codenameone.com/javadoc/com/codename1/ui/layouts/LayeredLayout.html[LayeredLayout] as follows:
@@ -723,11 +723,11 @@ IMPORTANT: The `calculatedInsetXXX` values here will be the same as the correspo
If no inset is specified, then it's assumed to be 0. This ensures compatibility with designs that were created before layered layout supported insets.
-===== Component references: linking components together
+===== Component references: Linking components together
If all you need to do is position a component relative to its parent container's bounds, then mere insets provide you with enough vocabulary to achieve this. But most UIs are more complex than this and require another concept: reference components. Often you will want to position a component relative to another child of the same container. This is also supported.
-For example, suppose I want to place a text field in the center of the form (both horizontally and vertically), and have a button placed beside it to the right. Positioning the text field is trivial (`setInset(textField, "auto")`), but there is no inset that you can provide that would position the button to the right of the text field. To do your goal, you need to set the text field as a reference component of the button's left inset - so that the button's left inset is "linked" to the text field. Here is the syntax:
+For example, suppose you want to place a text field in the center of the form (both horizontally and vertically), and have a button placed beside it to the right. Positioning the text field is trivial (`setInset(textField, "auto")`), but there is no inset that you can provide that would position the button to the right of the text field. To do your goal, you need to set the text field as a reference component of the button's left inset, which "links" the button's left inset to the text field. Here is the syntax:
[source,java]
----
@@ -758,7 +758,7 @@ The two active lines here are the last two:
NOTE: The reference position is defined as the distance, expressed as a fraction of the reference component's length on the inset's axis, between the reference component's leading (outer) edge and the point from which the inset is measured. A reference position of 0 means that the inset is measured from the leading edge of the reference component. A value of 1.0 means that the inset is measured from the trailing edge of the reference component. A value of 0.5 means that the inset is measured from the center of the reference component. Etc... Any floating point value can be used, though the most common values are 0 and 1.
-The definition above may make reference components and reference position seem more complex than it's. Some examples:
+The definition above may make reference components and reference position seem more complex than they are. Some examples:
. **For a top inset**:
.. referencePosition == 0 => the inset is measured from the top edge of the reference component.
@@ -974,7 +974,7 @@ It should be reasonably easy to port MiG code but you should notice the followin
=== Themes and styles
-Next you need to introduce you to 3 important terms in Codename One: Theme, Style and UIID.
+Next you need to introduce you to 3 important terms in Codename One: Theme, Style, and UIID.
Themes are similar conceptually to CSS, in fact they can be created with CSS syntax as this guide covers soon. The various Codename One ports ship with a native theme representing the appearance of the native OS UI elements. Every Codename One application has its own theme that derives the native theme and overrides behavior within it.
@@ -993,7 +993,7 @@ This is a text field component (user input field) but it will look like a `Label
Effectively you told the text field that it should use the UIID of `Label` when it's drawing itself. it's common to do tricks like that in Codename One. For example: `button.setUIID("Label")` would make a button appear like a label and allow you to track clicks on a `Label`.
-The UIID's translate the theme elements into a set of `Style` objects. These `Style` objects get their initial values from the theme but can be further manipulated after the fact. if I want to make the text field's foreground color red I could use this code:
+The UIID's translate the theme elements into a set of `Style` objects. These `Style` objects get their initial values from the theme but can be further manipulated after the fact. To make the text field's foreground color red you could use this code:
[source,java,title='setUIID on TextField']
----
@@ -1047,11 +1047,11 @@ This code is shorthand for resource file loading and for the installation of the
Modern Codename One projects ship with a `src/main/css/theme.css` file (or an equivalent stylesheet). Editing this file allows you to define UIIDs using standard CSS syntax together with Codename One–specific extensions such as `cn1-derive` for inheritance and the `#Constants` block for theme constants. Each time you build or run the project, the build tool compiles the CSS into the `theme.res` resource file automatically. Saving the CSS while the simulator is running will also trigger a refresh so you can iterate on styling.
-Because the CSS compiler produces the final resource file, you should treat the generated `theme.res` as an output artifact and keep your changes in the CSS source. Images referenced from CSS rules (for example: background images or multi-images) should be placed alongside the stylesheet so that they are picked up by the compiler. More details about the supported selectors and properties are covered in the dedicated CSS chapter later in this guide.
+Because the CSS compiler produces the final resource file, you should treat the generated `theme.res` as an output artifact and keep your changes in the CSS source. Images referenced from CSS rules (for example: background images or multi-images) should be placed alongside the stylesheet so that they're picked up by the compiler. More details about the supported selectors and properties are covered in the dedicated CSS chapter later in this guide.
=== GUI builder
-The GUI builder allows you to arrange components visually within a UI using drag, property sheets etc. With the GUI builder you can create elaborate, rich UI's without writing the layout code. Forms designed in the builder use auto-layout mode by default, which lets you position and resize components on a canvas while using `LayeredLayout` behind the scenes.
+The GUI builder allows you to arrange components visually within a UI using drag, property sheets etc. With the GUI builder you can create elaborate, rich UI's without writing the layout code. Forms designed in the builder use auto layout mode by default, which lets you position and resize components on a canvas while using `LayeredLayout` behind the scenes.
==== Hello world
@@ -1072,7 +1072,7 @@ This generates two files inside the `common` module:
. `common/src/main/guibuilder/com/example/MyForm.gui`
. `common/src/main/java/com/example/MyForm.java`
-NOTE: If you run the goal from inside the `common` submodule the files are written directly under that module's `src/main/...` directory. By default the goal creates a `Form`; pass `-DguiType=Dialog` or `-DguiType=Container` to generate one of the other types, and pass `-DautoLayout=false` to opt out of auto-layout mode.
+NOTE: If you run the goal from inside the `common` submodule the files are written directly under that module's `src/main/...` directory. By default the goal creates a `Form`; pass `-DguiType=Dialog` or `-DguiType=Container` to generate one of the other types, and pass `-DautoLayout=false` to opt out of auto layout mode.
To open the form in the GUI builder:
@@ -1116,7 +1116,7 @@ You will start by selecting the #Component Palette# and dragging a button into t
.You can drag any component you want from the palette to the main UI
image::img/new-gui-builder-drag-button.png[You can drag any component you want from the palette to the main UI,scaledwidth=50%]
-By default the auto-layout mode of the GUI builder uses layered layout to position components. Sides can be bound to a component or to the `Form`. You then use distance units to determine the binding behavior. The GUI builder tries to be "smart" and guesses your intention as you drag the components along.
+By default the auto layout mode of the GUI builder uses layered layout to position components. Sides can be bound to a component or to the `Form`. You then use distance units to determine the binding behavior. The GUI builder tries to be "smart" and guesses your intention as you drag the components along.
When you select the component you placed you can edit the properties of that component:
@@ -1129,9 +1129,9 @@ five property sheets per component:
- #Advanced Settings# -- These include features that aren't as common such as icon gap, mask etc.
-- #Events# -- By clicking a button in this tab a method will be added to the source file with a callback matching your component name. This will let's bind an event to a button, text field etc.
+- #Events# -- By clicking a button in this tab a method will be added to the source file with a callback matching your component name. This binds an event to a button, text field etc.
-- #Layout# -- You can determine the layout of the parent `Container` here. For auto-layout this should stay as layered layout, but you can nest other layout types in here
+- #Layout# -- You can determine the layout of the parent `Container` here. For auto layout this should stay as layered layout, but you can nest other layout types in here
- #Style Customization# -- This isn't a theme, if you want to customize the style of a specific component you can do that through this UI. The theme works on a more global/reusable level and this is designed for a specific component
@@ -1179,7 +1179,7 @@ image::img/gui-builder-java-and-gui-files.png[The java and GUI files in the hier
IMPORTANT: If you refactor (rename or move) the Java file it's connection with the GUI file will break. You need
to move/rename both
-You can edit the GUI file directly but changes won't map into the GUI builder unless you reopen it. These files should be under version control as they are the main files that change. The GUI builder file for the button and label code looks like this:
+You can edit the GUI file directly but changes won't map into the GUI builder unless you reopen it. These files should be under version control as they're the main files that change. The GUI builder file for the button and label code looks like this:
[source,xml]
----
@@ -1276,14 +1276,14 @@ The GUI builder uses the "magic comments" approach where code is generated into
You can write code within the class both by using the event mechanism, by writing code in the constructors or thru overriding functionality in the base class.
[[auto-layout-mode]]
-==== Auto-Layout mode
+==== Auto layout mode
-As of version 3.7, new forms created with the GUI Builder will use auto-layout mode. In this mode you can move and resize your components as you see fit. You aren't constrained to the positions dictated by the form's layout manager.
+As of version 3.7, new forms created with the GUI Builder will use auto layout mode. In this mode you can move and resize your components as you see fit. You aren't constrained to the positions dictated by the form's layout manager.
-.All forms designed in auto-layout mode use `LayeredLayout`
-NOTE: Auto-Layout Mode is built upon the inset support in LayeredLayout. Component positioning uses <>, not absolute positioning
+.All forms designed in auto layout mode use `LayeredLayout`
+NOTE: Auto layout mode is built upon the inset support in LayeredLayout. Component positioning uses <>, not absolute positioning
-As an example, let's drag a button onto a blank form and see what happens. The button will be "selected" initially after adding it, so you'll see its outline and resize handles for adjusting its size and position. You'll also see four floating labels (above, below, to the left, and to the right) that show the corresponding side's inset values and allow you to adjust them.
+As an example, drag a button onto a blank form and see what happens. The button will be "selected" initially after adding it, so you'll see its outline and resize handles for adjusting its size and position. You'll also see four floating labels (above, below, to the left, and to the right) that show the corresponding side's inset values and allow you to adjust them.
When a component is selected you can drag it to reposition it or use the resize handles to change its size. The floating inset labels update as you drag so you can fine tune spacing without leaving the canvas.
@@ -1291,31 +1291,33 @@ Press the mouse inside the bounds of the button and drag it around to reposition
===== The inset control
-Let's take a closer look at the inset control (the inset controls are the black buttons that appear to the top, bottom, left, and right of the selected component).
+Take a closer look at the inset control (the inset controls are the black buttons that appear to the top, bottom, left, and right of the selected component).
.The inset control allows you to change the inset size and units, toggle it between fixed and flexible, and link it to another component.
image::img/guibuilder-2-inset-control.png[Inset control,scaledwidth=5%]
Each control has three sections:
+// vale-skip: Microsoft.Percentages: "percent" is one of the named unit options in the GUI builder dropdown, used here as a unit name on equal footing with "millimetres" and "pixels", not as a quantity.
1. **The inset value drop-down menu**. This shows the current value of the inset (for example: 0mm, 25%, auto, etc...). If you click on this, it will open a menu that will allow you to change the units. If the inset is in millimetres, it will have options for pixels, and percent. If the inset is in percent, it will have options for pixels and millimetres. Etc.. It also includes a text field to enter a an inset value explicitly.
+
image::img/guibuilder-2-insets-dropdown-menu.png[Inset drop-down menu,scaledwidth=6%]
2. **The "Link" Button** image:img/guibuilder-2-link-button-unselected.png[Link button,scaledwidth=6%] - If the inset is linked to a reference component, then this button will be highlighted "blue," and hovering over it will highlight the reference component in the UI so that you can see which component it's linked to. Clicking on this button will open a dialog that will allow you to "break" this link. You can drag this button over any component in the form to "link."
+// vale-skip: Microsoft.Percentages: "percent" is a named unit option in the GUI builder dropdown, paired with "millimetres", not a quantity.
3. **The "Lock" Button"** image:img/guibuilder-2-inset-fixed-button.png[Inset fixed button,scaledwidth=6%] - This button allows you to toggle the inset between "flexible" (that's: auto) and "fixed" (that's: millimetres or percent).
===== Auto snap
-Notice the "auto-snap" checkbox that appears in the top-right corner of the GUI builder window.
+Notice the "autosnap" checkbox that appears in the top-right corner of the GUI builder window.
-.Auto-snap checkbox
-image::img/guibuilder-2-smart-insets-auto-snap-checkboxes.png[Auto-snap checkbox,scaledwidth=10%]
+.Autosnap checkbox
+image::img/guibuilder-2-smart-insets-auto-snap-checkboxes.png[Autosnap checkbox,scaledwidth=10%]
-Auto-snap does what it sounds like: It automatically snaps two components together when you drag them near each other. This is handy for linking components together without having to explicitly link them (using the "link" button). This feature is turned on by default. If auto-snap is turned off, you can still start a "snap" by holding down the ALT/Option key on your keyboard during the drag.
+Autosnap does what it sounds like: It automatically snaps two components together when you drag them near each other. This is handy for linking components together without having to explicitly link them (using the "link" button). This feature is turned on by default. If autosnap is turned off, you can still start a "snap" by holding down the ALT/Option key on your keyboard during the drag.
===== Smart insets
-Beside the "auto-snap" checkbox is another checkbox named `Smart Insets`.
+Beside the "autosnap" checkbox is another checkbox named `Smart Insets`.
.Smart insets checkbox
image::img/guibuilder-2-smart-insets-auto-snap-checkboxes.png[Smart insets checkbox,scaledwidth=10%]
@@ -1337,13 +1339,13 @@ This control pad also includes game-pad-like controls (up, down, left, right), t
1. **Arrow Keys** - Use the up/down/left/right arrow keys to nudge the selected component a little bit at a time. This is a convenient way to move the component to a position that's more precise than can be achieved with a mouse drag.
2. **Arrow Keys + SHIFT** - Hold down the SHIFT key while pressing an arrow key and it will "tab" the component to the next tab marker. The form has implicit tab markers at the edge of each component on the form.
-3. **ALT/Option Key + Click or Drag** - Holding down the option/alt key while clicking or dragging a component will resulting in "snapping" behaviour even if auto-snap is turned off.
+3. **ALT/Option Key + Click or Drag** - Holding down the option/alt key while clicking or dragging a component will resulting in "snapping" behaviour even if autosnap is turned off.
===== Sub-Containers
Sometimes, you may need to add sub-containers to your form to aid in grouping your components together. You can drag a container onto your form using the "Container" palette item (under "Core Components"). The default layout the subcontainer will be LayeredLayout so that you're able to position components within the sub-container with precision, like on the root container.
-You can also change the layout of subcontainers to another classical layout manager (for example: grid layout, box layout, etc..) and drag components directly into it. This is useful if parts of your form lend themselves to a different layout. As an example, let's drag a container onto the canvas that uses BoxLayout Y. (You can find this under the "Containers" section of the component palette).
+You can also change the layout of subcontainers to another classical layout manager (for example: grid layout, box layout, etc..) and drag components directly into it. This is useful if parts of your form lend themselves to a different layout. As an example, drag a container onto the canvas that uses BoxLayout Y. (You can find this under the "Containers" section of the component palette).
Drag the button (that was earlier on the form) over that container, and you should see a drop-zone become highlighted.
diff --git a/docs/developer-guide/component-selector.asciidoc b/docs/developer-guide/component-selector.asciidoc
index 932b420566..623035f8ca 100755
--- a/docs/developer-guide/component-selector.asciidoc
+++ b/docs/developer-guide/component-selector.asciidoc
@@ -10,7 +10,7 @@ With the new `ComponentSelector` class, you've adopted the following aspects of
1. **CSS-like Selection Sytax** - Support for a CSS-like syntax for "selecting" Components to be included in a set.
2. **Fluent API** - The API is fluent, meaning you can chain multiple method calls together and reduce typing.
-3. **Working on Sets** - Methods of this class operate on sets of components as if they are a single component. As you'll see, this can be incredibly powerful.
+3. **Working on Sets** - Methods of this class operate on sets of components as if they're a single component. As you'll see, this can be incredibly powerful.
4. **Effects** - Support for common effects on components such as fadeIn(), fadeOut(), slideDown(), and slideUp().
=== Motivation by example
@@ -46,9 +46,9 @@ Button slideUp = $(new Button("Slide Up")) // <1>
NOTE: For people like Shai who don't like the jQuery `$` syntax used for method names, you can or use the `select()` method as an alias for `$()`.
-Take a close look at that example, and imagine implementing that functionality the "old" way. It actually gets pretty hairy. Hopefully that example is enough to get you interested in using ComponentSelector.
+Take a close look at that example, and imagine implementing that functionality the "old" way. It actually gets pretty hairy. That example should be enough to get you interested in using ComponentSelector.
-Now let's go into some details of how it works and how to use it.
+Now look at some details of how it works and how to use it.
=== Selecting components
@@ -87,7 +87,7 @@ far You've glossed over the selector syntax by saying it's `like CSS`. Though it
==== Tags
-Some examples above mention the use of "tags." Tags are analogous to "classes" in CSS. The goal was to provide the same kind of functionality as CSS classes, but in Java the term "class" is a little busy, so the term "tag" is used instead. You can add as many tags to a component as you like. You can then use those tags to assist in your queries. Tags are added using the `addTags()` method, and they are removed using the `removeTags()` method.
+Some examples above mention the use of "tags." Tags are analogous to "classes" in CSS. The goal was to provide the same kind of functionality as CSS classes, but in Java the term "class" is a little busy, so the term "tag" is used instead. You can add as many tags to a component as you like. You can then use those tags to assist in your queries. Tags are added using the `addTags()` method, and they're removed using the `removeTags()` method.
Here is an example that uses tags to implement table striping so that even rows are a different color than odd rows in a table.
@@ -218,7 +218,7 @@ Button replace = $(new Button("Replace Fade/Slide"))
=== Component method wrappers
-Most mutator methods in `Component` and `Container` include a corresponding wrapper method in `ComponentSelector`. Some more common component subclasses have corresponding wrappers as well. For example, `addActionListener(event)` will add the event to all Buttons in the set. `setText(txt)` will set the text on all labels, text areas, and buttons. If there are other commonly used methods that you would like to see included in ComponentSelector, let's know, but you think you'll find the current state to be comprehensive.
+Most mutator methods in `Component` and `Container` include a corresponding wrapper method in `ComponentSelector`. Some more common component subclasses have corresponding wrappers as well. For example, `addActionListener(event)` will add the event to all Buttons in the set. `setText(txt)` will set the text on all labels, text areas, and buttons. If there are other commonly used methods that you would like to see included in ComponentSelector, file an issue, but you should find the current state to be comprehensive.
==== Tree navigation
diff --git a/docs/developer-guide/css.asciidoc b/docs/developer-guide/css.asciidoc
index 96aa6baf5f..2185a194fe 100644
--- a/docs/developer-guide/css.asciidoc
+++ b/docs/developer-guide/css.asciidoc
@@ -189,6 +189,7 @@ The `Default` selector is special in that it will set properties on the theme's
=== Custom properties
`cn1-source-dpi`::
+// vale-skip: proselint.Very: 'Very High Res' is a labelled DPI bucket in the Codename One multi-image API, not generic intensifier prose.
Used to specify source DPI for multi-image generation of background images. Accepted values: `0` (Not multi-image), `120` (Low res), `160` (Medium Res), `320` (Very High Res), `480` (HD), Higher than `480` (2HD). If not specified, the default value will be the value of the `defaultSourceDPIInt` theme constant, if specified, or `480`, if not specified.
`cn1-background-type`::
Used to explicitly specify the background-type that should be used for the class.
@@ -455,7 +456,7 @@ MyComponent {
.45deg gradient rendered at compile-time—uses background image
image::img/linear-gradient-45deg.png[45deg gradient rendered at compile-time - uses background image]
-The above example isn't supported natively because the gradient direction is 45 degrees. Codename One supports 0, 90, 180, and 270 degrees natively. So this would result in a background image being generated at compile-time with the appropriate gradient.
+The above example isn't supported natively because the gradient direction is 45 degrees. Codename One supports 0, 90, 180, and 270 degrees natively, so this example will result in a background image being generated at compile-time with the appropriate gradient.
[source,css]
----
@@ -467,7 +468,7 @@ MyComponent {
.Linear gradient with different alpha
image::img/linear-gradient-diff-alpha.png[Linear gradient with different alpha]
-The above linear-gradient isn't supported natively because the stop colors have different transparencies. The first color has an opacity of 0.5, and the second as an opacity of 1.0 (implicitly). So, this would result in the gradient being generated as an image at compile-time.
+The above linear-gradient isn't supported natively because the stop colors have different transparencies. The first color has an opacity of 0.5, and the second as an opacity of 1.0 (implicitly). The gradient will therefore be generated as an image at compile-time.
**Natively Supported `radial-gradient` Syntax**
@@ -602,7 +603,7 @@ Most application templates in https://www.codenameone.com/initializr[Codename On
==== Multi-Images as inputs
-If you've already generated images in all the appropriate sizes for all densities, you can provide them in the same file structure used by the Codename One XML resource files: The image path is a directory that contains images named after the density that they are intended to be used for. The possible names include:
+If you've already generated images in all the appropriate sizes for all densities, you can provide them in the same file structure used by the Codename One XML resource files: The image path is a directory that contains images named after the density that they're intended to be used for. The possible names include:
* `verylow.png`
* `low.png`
@@ -698,7 +699,7 @@ Images {
}
----
-Then in Java, I might do something like:
+Then in Java, you might do something like:
[source,java]
----
@@ -764,7 +765,7 @@ Component backgrounds in Codename One are a common source of confusion for newco
. Background Image - You can specify an image to be used as the background for a component. Codename One includes settings to define how the image is treated, for example, scale/fill, tile, etc. If a background image is specified, it will override the background color setting - unless the image has transparent regions.
. Image Border - You can define a 9-piece image border which will effectively cover the entire background of the component. If an image border is specified, it will override the background image of the component.
-A common scenario that I run into is trying to set the background color of a component and see no change when I preview your form because the style had an image background defined - which overrides your background color change.
+A common scenario you may run into is trying to set the background color of a component and seeing no change when you preview the form, because the style had an image background defined - which overrides the background color change.
The potential for confusion is mitigated somewhat, but still exists when using CSS. You can make your intentions explicit by adding the `cn1-background-type` property to your style. Possible values include:
@@ -899,9 +900,9 @@ If you add the following to your stylesheet, it will set the default font size t
}
----
-TIP: I have found that a value of 18 here gives best results across devices.
+TIP: A value of 18 here gives best results across devices.
-On the desktop, you may find that 18 is too big. You can define a default font size for tablet and desktop using `defaultDesktopFontSizeInt` and `defaultTabletFontSizeInt` respectively. I have found that a `defaultDesktopFontSizeInt` gives results that match the Mac OS default font size.
+On the desktop, you may find that 18 is too big. You can define a default font size for tablet and desktop using `defaultDesktopFontSizeInt` and `defaultTabletFontSizeInt` respectively. A `defaultDesktopFontSizeInt` value gives results that match the Mac OS default font size.
[source,css]
----
@@ -922,7 +923,7 @@ The conversion from "screen-independent pixels" or `mm` to physical pixels uses
Net result: the same `defaultFontSizeInt: 18` may render at slightly different absolute physical sizes on an iPhone vs. an Android device of similar physical size—typically the iOS rendering is the larger of the two when both are at the platform's default text size. If you need cross-platform pixel-perfect sizing, use platform overrides (`@iOS-platform-font-size`, etc., via media queries) rather than relying on the conversion math alone.
-NOTE: When a new iPhone model ships with a resolution that isn't yet in the iOS PPI table, mm-based measurements will use the fallback `460ppi`, which is correct to within a few percent for every modern iPhone. If you discover a model that should use a different value (for example a future Plus/Max device that returns to `458ppi`), file an issue so the table can be updated.
+NOTE: When a new iPhone model ships with a resolution that isn't yet in the iOS PPI table, mm-based measurements will use the fallback `460ppi`, which is correct to within 1-2% for every modern iPhone. If you discover a model that should use a different value (for example a future Plus/Max device that returns to `458ppi`), file an issue so the table can be updated.
****
==== `text-decoration`
diff --git a/docs/developer-guide/graphics.asciidoc b/docs/developer-guide/graphics.asciidoc
index f2697960ae..8c2fefd03f 100644
--- a/docs/developer-guide/graphics.asciidoc
+++ b/docs/developer-guide/graphics.asciidoc
@@ -2,7 +2,7 @@
WARNING: Drawing is considered a low-level API that might introduce some platform fragmentation.
-=== Basics - where and how do I draw manually
+=== Basics - where and how to draw manually
The https://www.codenameone.com/javadoc/com/codename1/ui/Graphics.html[Graphics] class handles drawing basics, shapes, images, and text. Developers never instantiate it directly; the Codename One API passes it in.
@@ -90,7 +90,7 @@ gradients to build sophisticated backgrounds and lighting effects.
=== Glass pane
The `GlassPane `in Codename One is inspired by the Swing `GlassPane` & `LayeredPane` with a few twists.
-You tried to imagine how Swing developers would have implemented the glass pane knowing what they do now about painters and Swings learning curve. But first: what is the glass pane?
+You tried to imagine how Swing developers would have implemented the glass pane knowing what they do now about painters and Swings learning curve. But first: what's the glass pane?
A typical Codename One application is essentially composed of 3 layers (this is a gross simplification though),
the background painters are responsible for drawing the background of all components including the main form. The
@@ -179,7 +179,7 @@ no real equivalent to perspective transform in Java SE that you could use.
You can show shape drawing with a simple example of a drawing app, that allows the user to tap the screen to draw a contour picture.
The app works by keeping a https://www.codenameone.com/javadoc/com/codename1/ui/geom/GeneralPath.html[GeneralPath]
-in memory, and continually adding points as bezier curves. Whenever a point is added, the path is redrawn to the screen.
+in memory, and adding points as bezier curves. Whenever a point is added, the path is redrawn to the screen.
The center of the app is the `DrawingCanvas` class, which extends link:https://www.codenameone.com/javadoc/com/codename1/ui/Component.html[Component].
@@ -263,7 +263,7 @@ image::img/lineto-example.png[lineTo example,scaledwidth=20%]
==== Using bezier curves
-Your previous implementation of addPoint() used lines for each segment of the drawing. Let's make a change
+Your previous implementation of addPoint() used lines for each segment of the drawing. Now make a change
to allow for smoother edges by using quadratic curves instead of lines.
Codename One's `GeneralPath` class includes two methods for drawing curves:
@@ -301,7 +301,7 @@ This change should be straight forward except, perhaps, the business with the `o
quadratic curves require two points (also to the implied starting point), you can't take the last tap
point and the current tap point. You need a point between them to act as a control point. This is where you get
the curve from. The control point works by exerting a sort of "gravity" on the line segment, to pull the line towards
-it. This results in the line being curved. I use the `odd` marker to alternate the control point between positions
+it. This results in the line being curved. The example uses the `odd` marker to alternate the control point between positions
above the line and below the line.
A drawing from the resulting app looks like:
@@ -312,9 +312,9 @@ image::img/quadto-example.png[Result of quadTo example,scaledwidth=20%]
==== Detecting platform support
-The `DrawingCanvas` example is a bit naive in that it assumes that the device supports the shape API. If I were
-to run this code on a device that doesn't support the https://www.codenameone.com/javadoc/com/codename1/ui/geom/Shape.html[Shape] API, it would draw a blank canvas where I
-expected your shape to be drawn. You can fall back gracefully if you make use of the
+The `DrawingCanvas` example is a bit naive in that it assumes that the device supports the shape API. Running this code on a device that doesn't support the https://www.codenameone.com/javadoc/com/codename1/ui/geom/Shape.html[Shape] API would draw a blank canvas where the shape was
+// vale-skip: Microsoft.Adverbs: "fall back gracefully" is a precise technical pattern (graceful degradation), not a softener.
+expected to be drawn. You can fall back gracefully if you make use of the
https://www.codenameone.com/javadoc/com/codename1/ui/Graphics.html#isShapeSupported()[`Graphics.isShapeSupported()`] method. For example:
[source,java]
@@ -390,7 +390,7 @@ public void paint(Graphics g){
=== Example: drawing an analog clock
-In the following sections, I will implement an analog clock component. This will show three key concepts
+The following sections implement an analog clock component. This will show three key concepts
in Codename One's graphics:
1. Using the `GeneralPath` class for drawing arbitrary shapes.
@@ -422,16 +422,16 @@ public class AnalogClock extends Component {
==== Setting up the parameters
-Before you actually draw anything, let's take a moment to figure out what values you need to know to
+Before you actually draw anything, take a moment to figure out what values you need to know to
draw an effective clock. Minimally, you need two values:
1. The center point of the clock.
2. The radius of the clock.
-Also, I am adding the following parameters to help customize how the clock is rendered:
+The clock also exposes the following parameters to help customize how it's rendered:
1. *The padding* (that's: the space between the edge of the component and the edge of the clock circle.
-2. *The tick lengths*. I will be using 3 different lengths of tick marks on this clock. The longest ticks will be
+2. *The tick lengths*. The example uses 3 different lengths of tick marks on this clock. The longest ticks will be
displayed at quarter points (that's: 12, 3, 6, and 9). Slightly shorter ticks will be displayed at the five-minute marks
(that's: where the numbers appear), and the remaining marks (corresponding with seconds) will be short.
@@ -547,7 +547,7 @@ for ( int i=1; i<=12; i++){
int tx = (int)(Math.cos(angleFrom3)*(r-longTickLen));
int ty = (int)(-Math.sin(angleFrom3)*(r-longTickLen));
- // For 6 and 12 we will shift number slightly so they are more even
+ // For 6 and 12 we will shift number slightly so they're more even
if ( i == 6 ){
ty -= charHeight/2;
} else if ( i == 12 ){
@@ -585,7 +585,7 @@ image::img/numbers.png[Drawing the numbers on the watch face,scaledwidth=40%]
==== Drawing the hands
The clock will include three hands: Hour, Minute, and Second. You will use a separate https://www.codenameone.com/javadoc/com/codename1/ui/geom/GeneralPath.html[GeneralPath] object
-for each hand. For the positioning/angle of each, I will use the following strategy:
+for each hand. For the positioning/angle of each, use the following strategy:
1. Draw the hand at the clock center pointing toward `12` (straight up).
2. Translate the hand slightly down so that it overlaps the center.
@@ -619,7 +619,7 @@ This requires you to calculate the angle. The `px` and `py` arguments constitute
which, in your case will be the clock center.
WARNING: The rotation pivot point is expected to be in absolute screen coordinates rather than relative
-coordinates of the component. So you need to get the absolute clock center position to perform the rotation.
+coordinates of the component. You therefore need to get the absolute clock center position to perform the rotation.
[source,java]
----
@@ -646,10 +646,10 @@ results on your form.
*Drawing the Minute And Hour Hands*:
-The mechanism for drawing the hour and minute hands is largely the same as for the minute hand. There are a
+The mechanism for drawing the hour and minute hands is largely the same as for the minute hand, with a
couple of added complexities though:
-1. You will make these hands trapezoidal, and almost triangular rather than using a simple line. So the
+1. You will make these hands trapezoidal, and almost triangular rather than using a simple line, which means the
`GeneralPath` construction will be slightly more complex.
2. Calculation of the angles will be slightly more complex because they need to consider many
parameters. For example: The hour hand angle is informed by both the hour of the day and the minute of the hour.
@@ -839,7 +839,7 @@ grow downward as illustrated below:
.The Codename One graphics coordinate space
image::img/coordinate_system.png[The Codename One graphics coordinate space,scaledwidth=20%]
-So the screen origin is at the top left corner of the screen. Given this information, consider the method
+The screen origin is at the top left corner of the screen. Given this information, consider the method
call on the https://www.codenameone.com/javadoc/com/codename1/ui/Graphics.html[Graphics] context `g`:
[source,java]
@@ -893,7 +893,7 @@ Usually, when you're drawing onto a `Graphics` context, you're doing so within t
are of your drawing. you're concerned with their relative location within the coordinate. You can leave
the positioning (and even sizing) of the coordinate up to Codename One. Thank you for reading.
-To show this, let's create a simple component called https://www.codenameone.com/javadoc/com/codename1/ui/geom/Rectangle.html[Rectangle] component, that draws a
+To show this, create a simple component called https://www.codenameone.com/javadoc/com/codename1/ui/geom/Rectangle.html[Rectangle] component, that draws a
rectangle on the screen. You will use the component's position and size to dictate the size of the rectangle to be
drawn. And you will keep a 5 pixel padding between the edge of the component and the edge of your rectangle.
@@ -929,7 +929,7 @@ transform and translation settings*.
2. *All coordinates passed to the context's transformation settings are considered to be screen coordinates, and
aren't subject to current transform and translation settings*.
-Let's take your `RectangleComponent` as an example. Suppose you want to rotate the rectangle by 45 degrees,
+Take your `RectangleComponent` as an example. Suppose you want to rotate the rectangle by 45 degrees,
your first try might look something like:
[source,java]
@@ -956,7 +956,7 @@ TIP: When performing rotations and transformations inside a `paint()` method, al
transformations at the end of the method so that it doesn't pollute the rendering pipeline for later components.
The behavior of this rotation will vary based on where the component is rendered on the screen. To
-show this, let's try to place five of these components on a form inside a https://www.codenameone.com/javadoc/com/codename1/ui/layouts/BorderLayout.html[BorderLayout] and see how it looks:
+show this, try to place five of these components on a form inside a https://www.codenameone.com/javadoc/com/codename1/ui/layouts/BorderLayout.html[BorderLayout] and see how it looks:
[source,java]
----
@@ -980,7 +980,7 @@ This may not be an intuitive outcome since you drew 10 rectangle components, be
rectangle. The reason is that the `rotate(angle)` method uses the screen origin as the pivot point for the rotation.
Components nearer to this pivot point will experience a less dramatic effect than components farther from it. In
your case, the rotation has caused all rectangles except the first one to be rotated outside the bounds of their
-containing component - so they are being clipped. A more sensible solution for your component would be to place
+containing component, and are therefore being clipped. A more sensible solution for your component would be to place
the rotation pivot point somewhere inside the component. That way all the components would look the same.
Some possibilities would be:
@@ -1023,7 +1023,7 @@ public void paint(Graphics g) {
image::img/rotation3.png[Rotating the rectangle with the center pivot point,scaledwidth=20%]
You could also use the `Graphics.setTransform()` class to apply rotations and other complex transformations
-(including 3D perspective transforms), but I will leave that for its own topic as it's a little bit more complex.
+(including 3D perspective transforms), but that belongs to its own topic as it's a little bit more complex.
==== Global alpha & Anti-Aliasing
@@ -1068,8 +1068,8 @@ This is because the `drawXXX()` methods for this component take coordinates rela
=== Images
Codename One has a few image types: loaded, RGB (built-in), RGB (Codename One), Mutable,
-EncodedImage, SVG, MultiImage, FontImage & Timeline. There are also URLImage, FileEncodedImage, FileEncodedImageAsync,
-`StorageEncodedImage`/Async that will be covered in the IO section.
+EncodedImage, SVG, MultiImage, FontImage & Timeline. URLImage, FileEncodedImage, FileEncodedImageAsync,
+and `StorageEncodedImage`/Async also exist, and are covered in the IO section.
All image types are seamless to use and will work with `drawImage` and various image related image
API's for the most part with caveats on performance etc.
@@ -1077,7 +1077,7 @@ API's for the most part with caveats on performance etc.
TIP: For animation images the code must invoke the `animate()` method on the image (this is done automatically by Codename One when placing the image as a background or as an icon! +
You need to do it if you invoke `drawImage` in code rather than use a built-in component).
-Performance and memory wise you should read the section below carefully and be aware of the image types you use.
+Performance and memory wise you should read the section below and be aware of the image types you use.
The Codename One designer tries to conserve memory and be "clever" by using `EncodedImage`. While these are great for low memory you need to understand the complexities of image locking and be aware that you might pay a penalty if you don't.
Here are the pros/cons and logic behind every image type. This covers the logic of how it’s created:
@@ -1110,7 +1110,7 @@ NOTE: This isn't the case for all images but it's common and you prefer calculat
==== The RGB image's
-two types of RGB constructed images that are different from one another but since they are both technically "RGB image's" you're bundling them under the same subsection.
+two types of RGB constructed images that are different from one another but since they're both technically "RGB image's" you're bundling them under the same subsection.
===== Internal
@@ -1159,7 +1159,7 @@ To verify that locking might be a problem you can launch the performance monitor
==== MultiImage
-Multi images don't physically exist as a concept within the Codename One API so there is no way to actually create them and they are in no way distinguishable from `EnclodedImage`.
+Multi images don't physically exist as a concept within the Codename One API so there is no way to actually create them and they're in no way distinguishable from `EnclodedImage`.
The built-in support for multi images is in the resource file loading logic where a MultiImage is decoded and the version that matches the current DPI is physically loaded. From that point on user code can treat it like any other `EnclodedImage`.
@@ -1301,7 +1301,7 @@ hard restriction because of the way `URLImage` is implemented.
****
The reason for the size restriction lies in the implementation of `URLImage`. `URLImage` is physically an animated image and so the UI thread tries to invoke its `animate()` method to refresh. The `URLImage` uses that call to check if the image was fetched and if not fetches it asynchronously.
-Once the image was fetched the `animate()` method returns true to refresh the UI. During the loading process the placeholder is shown, the reason for the restriction in size is that image animations can't "grow" the image. They are assumed to be fixed so the placeholder must match the dimensions of the resulting image.
+Once the image was fetched the `animate()` method returns true to refresh the UI. During the loading process the placeholder is shown, the reason for the restriction in size is that image animations can't "grow" the image. They're assumed to be fixed so the placeholder must match the dimensions of the resulting image.
****
The simple use case is pretty trivial:
@@ -1322,7 +1322,7 @@ TIP: Since https://www.codenameone.com/javadoc/com/codename1/ui/util/ImageIO.htm
it's working in Java SE, Android, iOS & Windows Phone.
If the file in the URL contains an image that's too big it will scale it to match the size of the placeholder precisely! +
-There is also an option to fail if the sizes don't match. Notice that the image that will be saved is the scaled
+An option also exists to fail if the sizes don't match. Notice that the image that will be saved is the scaled
image, this means you will have little overhead in downloading images that are the wrong size although you
will get some artifacts.
diff --git a/docs/developer-guide/img/cn1libs-refresh.png b/docs/developer-guide/img/cn1libs-refresh.png
deleted file mode 100644
index 01aa6dcf08..0000000000
Binary files a/docs/developer-guide/img/cn1libs-refresh.png and /dev/null differ
diff --git a/docs/developer-guide/io.asciidoc b/docs/developer-guide/io.asciidoc
index 6983e99d6e..312ad1bc49 100644
--- a/docs/developer-guide/io.asciidoc
+++ b/docs/developer-guide/io.asciidoc
@@ -2,7 +2,7 @@
=== JAR resources
-Resources packaged within the "JAR" don't belong in this section of the developer guide, but they are often confused with `Storage` and `FileSystemStorage`, so this is a good place to clarify them.
+Resources packaged within the "JAR" don't belong in this section of the developer guide, but they're often confused with `Storage` and `FileSystemStorage`, so this is a good place to clarify them.
You can place arbitrary files within the `src` directory of a Codename One project. The file is packaged into the final distribution. In standard Java SE you can do something like:
@@ -44,7 +44,7 @@ https://www.codenameone.com/javadoc/com/codename1/io/Storage.html[Storage] is ac
The `Storage` API also provides convenient methods to write objects to `Storage` and read them back with `readObject` and `writeObject`.
-TIP: Objects in `Storage` are deleted when an app is uninstalled, but they are retained between application updates. A notable exception is Android, which on some devices keeps objects in `Storage` after uninstalling an app. To force Android to remove them on uninstall, use the build hint `android.allowBackup=false`.
+TIP: Objects in `Storage` are deleted when an app is uninstalled, but they're retained between application updates. A notable exception is Android, which on some devices keeps objects in `Storage` after uninstalling an app. To force Android to remove them on uninstall, use the build hint `android.allowBackup=false`.
The sample code below demonstrates listing storage content, adding and viewing entries, and deleting entries:
@@ -216,12 +216,13 @@ hi.show();
.Simple sample of a tree for the FileSystemStorage API
image::img/filesystem-tree.png[Simple sample of a tree for the FileSystemStorage API,scaledwidth=20%]
+// vale-skip: Microsoft.HeadingPunctuation: "vs." is an abbreviation for "versus" — the period belongs to the abbreviation, not to a heading-end terminator.
==== Storage vs. file system
The question of storage vs. file system is often confusing for novice mobile developers. This embeds two separate questions:
- Why are there 2 API's where one would have worked?
-- Which one should I pick?
+- Which one should you pick?
The main reasons for the 2 API's are technical. Many OS's provide 2 ways of accessing data specific to the app and this is reflected within the API. For example: on Android the `FileSystemStorage` maps to API's such as `java.io.FileInputStream` whereas the `Storage` maps to `Context.openFileInput()`.
@@ -405,7 +406,7 @@ NetworkManager.getInstance().addToQueue(request);
TIP: Notice that overriding `buildRequestBody(OutputStream)` will work for `POST` requests and will replace writing the arguments
-IMPORTANT: You don't need to close the output/input streams passed to the request methods. They are implicitly cleaned up.
+IMPORTANT: You don't need to close the output/input streams passed to the request methods. They're implicitly cleaned up.
`NetworkManager` also supports synchronous requests which work in a similar way to `Dialog` through the `invokeAndBlock` call and thus don't block the EDT footnote:[Event Dispatch Thread] illegally. For example: you can do something like this:
@@ -446,7 +447,7 @@ NetworkManager.getInstance().updateThreadCount(4);
All the callbacks in the `ConnectionRequest` occur on the network thread and *not on the EDT*!
-There is one exception to this rule which is the `postResponse()` method designed to update the UI after the networking code completes.
+One exception to this rule exists: the `postResponse()` method, designed to update the UI after the networking code completes.
IMPORTANT: Never change the UI from a `ConnectionRequest` callback. You can either use a listener on the `ConnectionRequest`, use `postResponse()` (which is the exception to this rule) or wrap your UI code with `callSerially`.
@@ -583,7 +584,7 @@ two distinct placed where you can handle a networking error:
Notice that the `NetworkManager` error handler takes precedence thus allowing you to define a global policy for network error handling by consuming errors.
-For example: if I would like to block all network errors from showing anything to the user I could do something like this:
+For example: to block all network errors from showing anything to the user you could do something like this:
[source,java]
----
@@ -1246,7 +1247,7 @@ image::img/network-monitor.png[Debugging Network Connections,scaledwidth=20%]
Codename One includes a Network Monitor tool which you can access through the simulator menu option. This tool reflects all the requests made through the connection requests and displays them in the left pane. This allows you to track issues in your code/web service and see everything the is "going through the wire."
-This is a useful tool for optimizing and for figuring out what is happening with your server connection logic.
+This is a useful tool for optimizing and for figuring out what's happening with your server connection logic.
==== Simpler downloads
@@ -1268,7 +1269,7 @@ Both of which simplify the whole process of downloading a file.
Codename One has many ways to download an image and the general recommendation is the https://www.codenameone.com/javadoc/com/codename1/ui/URLImage.html[URLImage].
For example, the `URLImage` assumes that you know the size of the image in advance or that you're willing to resize it. In that regard it works great for some use cases but not so much for others.
-The download methods mentioned above are great alternatives but they are a bit verbose when working with images and don't provide fine grained control over the `ConnectionRequest` for example: making a `POST` request to get an image.
+The download methods mentioned above are great alternatives but they're a bit verbose when working with images and don't provide fine grained control over the `ConnectionRequest` for example: making a `POST` request to get an image.
TIP: Adding global headers is another use case but you can use
https://www.codenameone.com/javadoc/com/codename1/io/NetworkManager.html#addDefaultHeader-java.lang.String-java.lang.String-[addDefaultHeader]
@@ -1291,7 +1292,7 @@ This effectively maps the `ConnectionRequest` directly to a https://www.codename
`URLImage` is great. It changed the way you do some things in Codename One.
-For example, when you introduced it you didn't have support for the cache filesystem or for the JavaScript port. The cache filesystem is probably the best place for images of `URLImage` so supporting that as a target is a "no brainer" but JavaScript seems to work so why would it need a special case?
+For example, when you introduced it you didn't have support for the cache filesystem or for the JavaScript port. The cache filesystem is probably the best place for images of `URLImage` so supporting that as a target is an obvious choice, but JavaScript seems to work so why would it need a special case?
JavaScript already knows how to download and cache images from the web. `URLImage` is actually a step back from the things a good browser can do so why not use the native abilities of the browser when you're running there and fallback to using the cache filesystem if it's available and as a last resort go to storage...
@@ -1313,7 +1314,7 @@ If you do use this approach it would be far more efficient when running in the J
=== Rest API
-The `Rest` API makes it easy to invoke a restfull webservice without many of the nuances of `ConnectionRequest`. You can use it to define the HTTP method and start building based on that. if I want to get a parsed JSON result from a URL you could do:
+The `Rest` API makes it easy to invoke a restfull webservice without many of the nuances of `ConnectionRequest`. You can use it to define the HTTP method and start building based on that. To get a parsed JSON result from a URL you could do:
[source,java]
----
@@ -1327,14 +1328,14 @@ For a lot of REST requests this will fail because you need to add an HTTP header
Map jsonData = Rest.get(myUrl).acceptJson().getAsJsonMap().getResponseData();
----
-You can also do POST requests as easily:
+POST requests use the same builder pattern:
[source,java]
----
Map jsonData = Rest.post(myUrl).body(bodyValueAsString).getAsJsonMap().getResponseData();
----
-Notice the usage of post and the body builder method. There are MANY methods in the builder class that cover pretty much everything you would expect and then some when it comes to the needs of rest services.
+Notice the usage of post and the body builder method. MANY methods exist in the builder class that cover pretty much everything you would expect and then some when it comes to the needs of rest services.
Some highlights that are easy to miss:
@@ -1348,7 +1349,7 @@ Some highlights that are easy to miss:
* `.cacheMode(...)` and `.postParameters(...)` expose the same knobs as
`ConnectionRequest`, keeping you in the fluent API even for advanced tweaks.
-I changed the code in the kitchen sink webservice sample to use this API. I was able to make it shorter and more readable without sacrificing anything.
+The code in the kitchen sink webservice sample was updated to use this API. The result is shorter and more readable without sacrificing anything.
==== Rest in practice - twilio
@@ -1442,7 +1443,7 @@ if(result.getResponseData() != null) {
The Webservice Wizard can be invoked directly from the plugin. It generates stubs for the client side that allow performing simple method invocations on the server. It also generates a servlet that can be installed on any servlet container to intercept client side calls.
-There are limits to the types of values that can be passed through the webservice wizard protocol but it's highly efficient since it's a binary protocol and extensible thru object externalization. All methods are provided both as asynchronous and synchronous calls for the convenience of the developer.
+The types of values that can be passed through the webservice wizard protocol have limits, but it's highly efficient since it's a binary protocol and extensible thru object externalization. All methods are provided both as asynchronous and synchronous calls for the convenience of the developer.
.The first step in creating a client/server connection using the webservice wizard is to create a web application
image::img/webservice-wizard-step1-create-webapp.png[The first step in creating a client/server connection using the webservice wizard is to create a web application,scaledwidth=40%]
@@ -1450,7 +1451,7 @@ image::img/webservice-wizard-step1-create-webapp.png[The first step in creating
.Any name will do
image::img/webservice-wizard-step2-any-name-will-do.png[Any name will do,scaledwidth=40%]
-You should have a server setup locally. I use Tomcat since it's trivial and I don't need much but there are many great Java webservers out there and this should work with all them!
+You should have a server setup locally. The example uses Tomcat since it's trivial and the requirements are modest, but many great Java webservers exist and this should work with all them!
.Setup your webserver in the IDE
image::img/webservice-wizard-step3-setup-your-webserver.png[Setup your webserver in the IDE,scaledwidth=40%]
@@ -1589,7 +1590,7 @@ non-trivial as it requires a delicate balance if you want to test the server res
HTTP provides two ways to do that the https://en.wikipedia.org/wiki/HTTP_ETag[ETag] and
https://developer.mozilla.org/en-You/docs/Web/HTTP/Headers/Last-Modified[Last-Modified]. While both are
-great they are non-trivial to use and by no definition seamless.
+great they're non-trivial to use and by no definition seamless.
You added an experimental feature to connection request that allows you to set the caching mode to one of
4 states either globally or per connection request:
@@ -2050,20 +2051,20 @@ The properties are effectively the getters/setters for example: `subject`, `when
that are crucial:
- They can be manipulated in runtime by a tool that had no knowledge of them during compile time
-- They are observable - a tool can monitor changes to a value of a property
+- They're observable - a tool can monitor changes to a value of a property
- They can have meta-data associated with them
These features are crucial since properties allow you all kinds of magic for example: hibernate/ORM uses properties to bind
Java objects to a database representation, jaxb does it to parse XML directly into Java objects and GUI builders
-use them to let's customize UI's visually.
+use them to customize UI's visually.
-POJO's don't support most of that so pretty much all Java based tools use a lot of reflection & bytecode manipulation. This works but has a lot of downsides for example: say I want to map an object both to the Database and to XML/JSON.
+POJO's don't support most of that so pretty much all Java based tools use a lot of reflection & bytecode manipulation. This works but has a lot of downsides for example: say you want to map an object both to the Database and to XML/JSON.
Would the bytecode manipulation collide?
Would it result in duplicate efforts?
-And how do I write custom generic code that uses such abilities? Do I need to manipulate the VM?
+And how do you write custom generic code that uses such abilities? Do you need to manipulate the VM?
==== Properties in Java
@@ -2087,7 +2088,7 @@ public class Meeting implements PropertyBusinessObject {
}
----
-This looks a bit like a handful so let's start with usage which might clarify a few things then dig into the class itself.
+This looks a bit like a handful, so start with usage which might clarify a few things, then dig into the class itself.
When you used a POJO you did this:
@@ -2151,7 +2152,7 @@ that's why you've the index object and the `PropertyBusinessObject` interface (w
The `PropertyIndex` class provides meta data for the surrounding class including the list of the properties within.
It allows enumerating the properties and iterating over them making them accessible to all tools.
-Furthermore all properties are observable with the property change listener. I can write this to instantly print
+Furthermore all properties are observable with the property change listener. You can write this to instantly print
out any change made to the property:
[source,java]
@@ -2216,8 +2217,8 @@ but sqlite isn't big iron so it might be good enough.
===== Constructors
-One of the problematic issues with constructors is that any change starts propagating everywhere. If I have
-fields in the constructor and I add a new field later I need to keep the old constructor for compatibility.
+One of the problematic issues with constructors is that any change starts propagating everywhere. If you have
+fields in the constructor and you add a new field later you need to keep the old constructor for compatibility.
you added a new syntax:
@@ -2247,7 +2248,7 @@ meet.when.set(new Date());
===== Seamless serialization
-Lets assume I have an object called `Contacts` which includes contact information of contact for example:
+Suppose you have an object called `Contacts` which includes contact information for example:
[source,java]
----
@@ -2403,7 +2404,7 @@ You ignored functions, joins, transactions and a lot of other SQL capabilities.
You can use SQL directly to use all these capabilities for example: if you begin a transaction before inserting/updating or deleting this will work as advertised but if a rollback occurs your mapping will be unaware of that so you will need to re-fetch the data.
-You will notice you mapped auto-increment so you will try to map things that make sense for various use cases, if you've such a use case you'd appreciate pull requests and feedback on the implementation.
+You will notice you mapped autoincrement so you will try to map things that make sense for various use cases, if you've such a use case you'd appreciate pull requests and feedback on the implementation.
====== Caching/Collision
@@ -2436,7 +2437,7 @@ This would customize all entry keys to start with `MySettings-` instead of `Sett
===== UI binding
-One of the bigger features of properties are their ability to bind UI to a property. For example: if you continue the sample above with the `Contact` class let's say you've a text field on the form and you want the property (which you mapped to the database) to have the value of the text field. You could do something like this:
+One of the bigger features of properties are their ability to bind UI to a property. For example: if you continue the sample above with the `Contact` class, say you've a text field on the form and you want the property (which you mapped to the database) to have the value of the text field. You could do something like this:
[source,java]
----
@@ -2510,7 +2511,7 @@ resp.add(c.rank.getLabel()).
add(rankTf);
----
-<1> Notice I use the label of the property which allows better encapsulation
+<1> Notice the use of the property's label, which allows better encapsulation
<2> You can bind picker seamlessly
<3> You can bind many radio buttons to a single property to allow the user to select the gender, notice that labels and values can be different for example: "Male" selection will translate to "M" as the value
<4> Numeric bindings "work"
@@ -2523,9 +2524,9 @@ image::img/properties-demo-binding.png[Properties form for the contact,scaledwid
// vale-skip: Microsoft.ComplexWords/TooWordy — 'an additional version' reads correctly; 'an more version' is ungrammatical.
You skipped a couple of fact about the `bind()` method. It has an additional version that accepts a `ComponentAdapter` which allows you to adapt the binding to any custom 3rd party component. that's a bit advanced for now but I might discuss this later.
-For example, the big thing I "skipped" was the return value... `bind` returns a `UiBinding.Binding` object when performing the bind. This object allows you to manipulate aspects of the binding specifically unbind a component and also manipulate auto commit for a specific binding.
+For example, the big thing "skipped" earlier was the return value... `bind` returns a `UiBinding.Binding` object when performing the bind. This object allows you to manipulate aspects of the binding specifically unbind a component and also manipulate auto commit for a specific binding.
-Auto commit determines if a property is changed instantly or on `commit`. This is useful for a case where you've an "OK" button and want the changes to the UI to update the properties when "OK" is pressed (this might not matter if you keep different instances of the object). When auto-commit is on (the default which you can change through `setAutoCommit` in the `UiBinding`) changes reflect instantly, when it's off you need to explicitly call `commit()` or `rollback()` on the `Binding` class.
+Auto commit determines if a property is changed instantly or on `commit`. This is useful for a case where you've an "OK" button and want the changes to the UI to update the properties when "OK" is pressed (this might not matter if you keep different instances of the object). When autocommit is on (the default which you can change through `setAutoCommit` in the `UiBinding`) changes reflect instantly, when it's off you need to explicitly call `commit()` or `rollback()` on the `Binding` class.
`commit()` applies the changes in the UI to the properties, `rollback()` restores the UI to the values from the properties object (useful for a "reset changes" button).
@@ -2550,7 +2551,7 @@ Container cnt = iui.createEditUI(myContact, true); // <3>
<2> This implements the `gender` toggle button selection, you provide a hint to the UI so labels and values differ
<3> You create the UI from the screenshot above with one line and it's seamlessly bound to the properties of myContact. The second argument indicates the "auto commit" status.
-This still carries most of the flexibilities of the regular binding for example: I can still get a binding object using:
+This still carries most of the flexibilities of the regular binding for example: you can still get a binding object using:
[source,java]
----
diff --git a/docs/developer-guide/performance.asciidoc b/docs/developer-guide/performance.asciidoc
index 3a06620cdf..8dc7fbdb95 100644
--- a/docs/developer-guide/performance.asciidoc
+++ b/docs/developer-guide/performance.asciidoc
@@ -33,12 +33,14 @@ When faced with size issues make sure to check the size of your res file, if you
=== Improving performance
-There are quite a few things you can do as a developer to improve the performance and memory footprint of a Codename One application. This sometimes depends on specific device behaviors but some tips here are true for all devices.
+As a developer you can do quite a few things to improve the performance and memory footprint of a Codename One application. This sometimes depends on specific device behaviors but some tips here are true for all devices.
The simulator contains some tools to measure performance overhead of a specific component and also detect EDT blocking logic. Other than that follow these guidelines to create more performance code:
-* *Avoid round rect borders* - they have a huge overhead on all platforms. Use image borders instead (counter intuitively they are MUCH faster)
+* *Avoid round rect borders* - they have a huge overhead on all platforms. Use image borders instead (counter intuitively they're MUCH faster)
+// vale-skip: Microsoft.Adverbs: "perform poorly" is a precise performance descriptor (slow / inefficient), not a softener.
* *Avoid Gradients* - they perform poorly on most OS's. Use a background image instead
+// vale-skip: Microsoft.Adverbs: "tiling it repeatedly" describes the precise rendering operation, not authorial padding.
* *Use larger images* when tiling or building image borders, using a 1 pixel (or event a few pixels) wide or high image and tiling it repeatedly can be expensive
* *Shrink resource file sizes* - Otherwise data might get collected by the garbage collector and reloading data might be expensive
* *Check that you don't have too many image lock misses* - this is discussed in the graphics section
@@ -106,7 +108,7 @@ For low-level loops (for example, Base64 encode/decode):
* Prefer simple loop bodies with predictable branches.
* Cache decode/lookup tables in primitive arrays (`int[]` lookup tables can reduce per-iteration conversion overhead).
-* Avoid adding “defensive” branches in the inner-most loop unless they are required for correctness in production inputs.
+* Avoid adding “defensive” branches in the inner-most loop unless they're required for correctness in production inputs.
===== Build configuration matters
@@ -130,7 +132,7 @@ NEON or SSE register you can hold sixteen bytes, eight 16-bit integers, four
32-bit integers, or four 32-bit floats, and a single instruction such as
`VADD` or `PADDD` then operates on every lane in parallel. For data-parallel
workloads—Base64 encode/decode, pixel blending, alpha-mask compositing,
-colour-channel manipulation, table lookups—SIMD typically delivers a 3× to
+color-channel manipulation, table lookups—SIMD typically delivers a 3× to
10× speedup over scalar code.
Modern mobile and desktop CPUs expose several SIMD instruction sets:
@@ -147,7 +149,7 @@ performs better where native SIMD is available.
==== When to use SIMD
-SIMD is worth reaching for when *all* of the following hold:
+SIMD is worth reaching for when *all* the following hold:
* You are processing a buffer of at least dozens of elements at a time.
* The operation per element is simple (add, min, blend, table lookup,
@@ -182,7 +184,7 @@ The returned object is safe to cache in a field as long as the cache is
refreshed across simulator restarts (the simulator may swap the backing
implementation).
-==== Allocation: alignment and provenance
+==== Allocation: Alignment and provenance
SIMD load/store instructions prefer—and on some architectures require—
that the memory address they read from or write to be a multiple of the
diff --git a/docs/developer-guide/security.asciidoc b/docs/developer-guide/security.asciidoc
index a37bbf7a9f..b1755223b1 100644
--- a/docs/developer-guide/security.asciidoc
+++ b/docs/developer-guide/security.asciidoc
@@ -2,7 +2,7 @@
Security is a "big word." It implies many things and that's also true for the mobile app development so before you get started lets try to define the scope of security.
-You will deal with application security and its communication mechanisms while ignoring everything beyond that scope. Let's start with a simple fact:
+You will deal with application security and its communication mechanisms while ignoring everything beyond that scope. Start with a simple fact:
____
Codename One applications are secure on the devices by the nature of the mobile OS security.
@@ -23,7 +23,7 @@ Each section below discusses some attack vectors against applications and how th
=== Constant obfuscation
-One of the first things a hacker will do when compromising an app is look at it. For example, if I want to exploit a bank's login UI I would look at the label next to the login and then search for it in the decompiled code. if the UI has the String "enter user name and password" I can search for that.
+One of the first things a hacker will do when compromising an app is look at it. For example, an attacker wanting to exploit a bank's login UI would look at the label next to the login and then search for it in the decompiled code. If the UI has the String "enter user name and password" they can search for that.
It won't lead directly to a hack or exploit, but it will narrow down the area of the code where you should look and it makes the first step that much easier. Obfuscation helps as it removes descriptive method names but it can't hide the Strings you use in constants. If an app has a secret key within obfuscating it can make a difference (albeit a slight difference).
@@ -54,11 +54,11 @@ You might be concerned about the secret, then this would make it slightly harder
private static final String SECRET = Util.xorDecode("RW1tI3Ema219KmpidGFhdTFhdnE1Yn9xajQ1MjM=");
----
-Notice that this is **not secure**, if you've a crucial value that must not be found you need to store it in the server. There is no alternative because everything that's sent to the client can be compromised by a determined hacker.
+Notice that this **isn't secure**, if you've a crucial value that must not be found you need to store it in the server. No alternative exists because everything that's sent to the client can be compromised by a determined hacker.
TIP: Use the comment to help you find the string in the code
-Codename One's built-in user-specific constants are obfuscated with this method. For example, an app built with Codename One carries some internal data such as the user who built the app, which is now obfuscated. The following small app encodes strings so you can copy and paste them into your app easily:
+Codename One's built-in user-specific constants are obfuscated with this method. For example, an app built with Codename One carries some internal data such as the user who built the app, which is now obfuscated. The following small app encodes strings so you can copy and paste them into your app:
[source,java]
----
@@ -190,7 +190,7 @@ Android launched with RSA1024/SHA1 as the signing certificates. This was good en
==== The bad news
-There is a downside...
+A downside exists...
Google introduced that capability in Android 4.3 so using these new keys will break compatibility with older devices. If you're building a highly secure app this is probably a tradeoff you should accept. If not this might not be worth it for some theoretical benefit.
@@ -214,11 +214,11 @@ TIP: What if the router grabs the server's certificate and communicates with Goo
This won't work since the data you send to the server is encrypted with the certificate from the server.
-TIP: So what if the router sends its own "fake certificate"?
+TIP: What if the router sends its own "fake certificate"?
That won't work either. All certificates are signed by a "certificate authority" indicating that a google.com certificate is valid.
-TIP: What if I was able to get your fake certificate authorized by a real certificate authority?
+TIP: What if an attacker could get a fake certificate authorized by a real certificate authority?
**that's a problem!**
diff --git a/docs/developer-guide/signing.asciidoc b/docs/developer-guide/signing.asciidoc
index 23c27181e1..89c9a43c4b 100644
--- a/docs/developer-guide/signing.asciidoc
+++ b/docs/developer-guide/signing.asciidoc
@@ -9,11 +9,11 @@ The good news is that this is a "one time issue" and once it's done the work bec
This section uses some terms that you might not be familiar with if you haven't used cryptography or built mobile apps before. Here is a quick "FAQ" covering some common terms/concepts in this section.
-==== What is a certificate
+==== What's a certificate
Certificates use cryptographic principals to "sign" data (for example, an application). Think of them as you would think of a company stamp, you use them to sign an app so users know who it's from.
-==== What is provisioning
+==== What's provisioning
Provisioning provides the hints/guidelines for the application install. For example, if an application needs some service from the OS such as push it can request that with provisioning.
@@ -27,7 +27,7 @@ Android uses self signed certificates which don't use a signing authority so any
The logic with the Android approach is that a signature indicates that you're the same person who earlier shipped the app. Hence an app will be updated with the exact same certificate.
-==== What is UDID
+==== What's UDID
The UDID is the Universal Device Identifier. It identifies mobile devices uniquely, notice that some operating systems for example, iOS block access to this value due to privacy concerns.
@@ -36,17 +36,17 @@ You need the iOS device UDID value during development to add the device into the
IMPORTANT: don't use an app to get the UDID! +
Most return the wrong value. Use the Finder device summary (macOS), Apple Configurator, or a trusted service such as http://get.udid.io/ which seems to work rather well
-==== Should I reuse the same certificate for all apps
+==== Should you reuse the same certificate for all apps
For iOS yes. it's designed to work in that way.
-You would recommend it for all platforms for simplicity but some developers prefer creating per-application certificates for Android. The advantage here is that you can transfer ownership of the application later on without giving away what is effectively "you house keys."
+You would recommend it for all platforms for simplicity but some developers prefer creating per-application certificates for Android. The advantage here is that you can transfer ownership of the application later on without giving away what's effectively "your house keys."
[[certificate-wizard]]
=== iOS signing wizard
-Codename One features a wizard to generate certificates/provisioning for iOS without requiring a Mac or deep understanding of the signing process for iOS. There is still support for manually generating the P12/provisioning files when necessary but for most intents and purposes using the wizard will simplify this error prone process.
+Codename One features a wizard to generate certificates/provisioning for iOS without requiring a Mac or deep understanding of the signing process for iOS. Support still exists for manually generating the P12/provisioning files when necessary but for most intents and purposes using the wizard will simplify this error prone process.
To generate your certificates and profiles, open project's properties and click on "iOS" in the left menu. This will show the "iOS Signing" panel that includes fields to select your certificates and mobile provisioning profiles.
@@ -109,8 +109,8 @@ A typical iOS app has at least two certificates:
- Distribution - this is used when you're ready to upload your app to App Store Connect whether for final shipping or beta testing. Notice that you can't install a distribution build on your own device. You need to upload it to App Store Connect.
-- There are two push certificates, they are separate from the signing certificates. don't confuse them! +
-They are used to authenticate with Apple when sending push messages.
+- Two push certificates exist, separate from the signing certificates. don't confuse them! +
+They're used to authenticate with Apple when sending push messages.
****
==== App IDs and provisioning profiles
@@ -208,7 +208,7 @@ In the devices section add device ids for the development devices you want to su
.Add devices
image::img/ios-add-devices.png[Add devices,scaledwidth=30%]
-Create an application id; it should match the package identifier of your application perfectly!
+Create an application id; it must match the package identifier of your application exactly!
.Create application id
image::img/ios-create-app-id.png[Create application id,scaledwidth=30%]
diff --git a/docs/developer-guide/styles/config/vocabularies/CodenameOne/accept.txt b/docs/developer-guide/styles/config/vocabularies/CodenameOne/accept.txt
new file mode 100644
index 0000000000..57bc7fe9fd
--- /dev/null
+++ b/docs/developer-guide/styles/config/vocabularies/CodenameOne/accept.txt
@@ -0,0 +1,13 @@
+Codename
+CodenameOne
+ParparVM
+RoboVM
+asciidoctor
+asciidoc
+adoc
+Lollipop
+[Ee]xtensible
+oversized
+Java ME
+Java SE
+Java EE
diff --git a/scripts/developer-guide/find_unused_images.py b/scripts/developer-guide/find_unused_images.py
index cc0215ad2c..1742080724 100755
--- a/scripts/developer-guide/find_unused_images.py
+++ b/scripts/developer-guide/find_unused_images.py
@@ -8,6 +8,19 @@
from typing import Iterable, List
ASCIIDOC_EXTENSIONS = {".adoc", ".asciidoc"}
+IMAGE_EXTENSIONS = {
+ ".png",
+ ".jpg",
+ ".jpeg",
+ ".gif",
+ ".svg",
+ ".webp",
+ ".bmp",
+ ".ico",
+ ".tif",
+ ".tiff",
+ ".pdf",
+}
def iter_text_files(root: Path) -> Iterable[Path]:
@@ -46,6 +59,11 @@ def main() -> None:
for image_path in sorted(image_dir.rglob("*")):
if not image_path.is_file():
continue
+ # Skip non-image artifacts that may live next to images (.gitkeep,
+ # OS-generated thumbnails, editor metadata). The unused-images check
+ # only meaningfully applies to image files referenced from prose.
+ if image_path.suffix.lower() not in IMAGE_EXTENSIONS:
+ continue
rel_path = image_path.relative_to(doc_root).as_posix()
if any(rel_path in text for text in contents):
continue
@@ -65,8 +83,8 @@ def main() -> None:
print("Unused images detected:")
for rel_path in unused:
print(f" - {rel_path}")
- else:
- print("No unused images found.")
+ raise SystemExit(1)
+ print("No unused images found.")
if __name__ == "__main__":