--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Tnitter minimal portable app listing the latest Tnits
+#
+# This app use push notification to be updated in the second a new Tnit is posted.
+# So it begins by requesting a full list and the successive push request for updates.
+# If an error occurs, the full list is requested again after a short delay.
+#
+# This approach may still miss a few updates it they happen too close to one another.
+# To solve this, we could send an id for the latest known Tnit to the server.
+# Which could recognize if a client is not up to date.
+module tnitter_app is
+ app_name "Tnitter"
+ app_version(0, 1, git_revision)
+ app_namespace "net.xymus.tnitter"
+end
+
+import app::ui
+import app::http_request
+import app::data_store
+import android::aware
+import json::serialization
+
+import model
+
+# Delay in seconds before the next request after an error
+fun request_delay_on_error: Int do return 60
+
+redef class App
+ redef fun on_create
+ do
+ # Create the main window
+ window = new TnitterWindow
+ super
+ end
+end
+
+# Main window
+class TnitterWindow
+ super Window
+
+ private var layout = new VerticalLayout(parent=self)
+ private var list_posts = new ListLayout(parent=layout)
+ private var lbl_init = new Label(parent=list_posts, text="Awaiting connection to server")
+
+ # Request an initial full update
+ init do (new ListPostRequest(self, "rest/list?count=16", false)).start
+
+ # Request a full update after a delay
+ fun request_full_list_on_error
+ do
+ (new ListPostRequest(self, "rest/list?count=16", true)).start
+ end
+
+ # Open a push notification connection and thread
+ fun request_push_notification
+ do
+ (new ListPostRequest(self, "push/", false)).start
+ end
+
+ # Update the screen to show the new `posts`
+ fun apply_update(posts: Array[Post])
+ do
+ layout.remove list_posts
+ list_posts = new ListLayout(parent=layout)
+ for post in posts do
+ var line = new VerticalLayout(parent=list_posts)
+ var author = new LabelAuthor(parent=line, text="@"+post.user)
+ var text = new Label(parent=line, text=post.text)
+ end
+ end
+end
+
+# Label to display the author's name
+#
+# By default, this view is identical a `Label`,
+# but if can be refined per platforms.
+class LabelAuthor super Label end
+
+# ---
+# Async RESTful actions
+
+# URI of the remote RESTful server
+fun tnitter_server_uri: String do return "http://localhost:8080"
+
+# `AsyncHttpRequest` with services to act on the windows of the app
+abstract class AsyncTnitterRequest
+ super AsyncHttpRequest
+
+ private var window: TnitterWindow
+
+ redef fun rest_server_uri do return tnitter_server_uri
+
+ redef var rest_action
+
+ # Should this request be delayed by `request_delay_on_error` seconds?
+ var delay: Bool
+
+ redef fun main
+ do
+ if delay then nanosleep(request_delay_on_error, 0)
+ return super
+ end
+end
+
+# Async request to list latest posts, either immediately or by push notification
+#
+# Implementation note:
+# This class could as well be merged with `AsyncTnitterRequest` or have two versions,
+# one for the immediate update and one for the push notification.
+# We chose this structure for simplicity of the example,
+# and as more services may be added in the future.
+# If these future services expect data of a different format,
+# they will need a different `on_load` but could still use `AsyncTnitterRequest`.
+class ListPostRequest
+ super AsyncTnitterRequest
+
+ redef fun on_load(posts)
+ do
+ # Deal with server-side errors
+ if posts isa Error then
+ print_error "Server Error: '{posts.message}' from '{rest_server_uri / rest_action}'"
+ return
+ end
+
+ # Type check
+ if not posts isa Array[Post] then
+ print_error "Error: Got '{posts or else "null"}'"
+ return
+ end
+
+ # Update UI and prepare for the next update
+ window.apply_update posts
+ window.request_push_notification
+ end
+
+ redef fun on_fail(error)
+ do
+ print "Warning: Request {rest_server_uri/rest_action} failed with {error}"
+ window.request_full_list_on_error
+ end
+end
+
+# ---
+# Services
+
+redef class Deserializer
+ redef fun deserialize_class(name)
+ do
+ # This is usually generated using `nitserial`,
+ # but for a single generic class it is easier to implement manually
+
+ if name == "Array[Post]" then return new Array[Post].from_deserializer(self)
+ return super
+ end
+end
--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Android version of the Tnitter app
+module tnitter_app_android is
+ android_api_target 15
+end
+
+import tnitter_app
+
+import android::ui
+import android::http_request
+import android::portrait
+
+redef class LabelAuthor
+ init do native.set_text_appearance(app.native_activity, android_r_style_text_appearance_large)
+end