Skip to content

ServiceStackApps/TechStacksAndroidApp

Folders and files

NameName
Last commit message
Last commit date

Latest commit

History

10 Commits

Repository files navigation

TechStacks Android App

To demonstrate Java Native Types in action we've ported the Swift TechStacks iOS App to a native Java Android App to showcase the responsiveness and easy-of-use of leveraging Java Add ServiceStack Reference in Android Projects.

The Android TechStacks App can be downloaded for free from the Google Play Store:

iOS inspired Data Binding

As there's no formal data-binding solution in Android we've adopted a lightweight iOS-inspired Key-Value-Observable-like data-binding solution in Android TechStacks in order to maximize knowledge-sharing and ease porting between native Swift iOS and Java Android Apps.

Similar to the Swift TechStacks iOS App, all web service requests are encapsulated in a single App.java class and utilizes Async Service Client API's in order to maintain a non-blocking and responsive UI.

Registering for Data Updates

In iOS, UI Controllers register for UI and data updates by implementing *DataSource and *ViewDelegate protocols, following a similar approach, Android Activities and Fragments register for Async Data callbacks by implementing the Custom interface AppDataListener below:

publicstaticinterfaceAppDataListener{publicvoidonUpdate(AppDatadata, DataTypedataType)}

Where Activities or Fragments can then register itself as a listener when they're first created:

@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState); App.getData().addListener(this)}

Data Binding Async Service Responses

Then in onCreateView MainActivity calls the AppData singleton to fire off all async requests required to populate it's UI:

publicViewonCreateView(LayoutInflaterinflater, ViewGroupcontainer, Bundlestate){App.getData().loadAppOverview(); ... }

Where loadAppOverview() makes an async call to the AppOverview Service, storing the result in an AppData instance variable before notifying all registered listeners that DataType.AppOverview has been updated:

publicAppDataloadAppOverview(){client.getAsync(newAppOverview(), newAsyncResult<AppOverviewResponse>(){@Overridepublicvoidsuccess(AppOverviewResponseresponse){appOverviewResponse = response; onUpdate(DataType.AppOverview)} }); returnthis}

Returning this allows expression chaining, reducing the boilerplate required to fire off multiple requests

Calling onUpdate() simply invokes the list of registered listeners with itself and the enum DataType of what was changed, i.e:

publicvoidonUpdate(DataTypedataType){for (AppDataListenerlistener : listeners){listener.onUpdate(this, dataType)} }

The Activity can then update its UI within the onUpdate() callback by re-binding its UI Controls when relevant data has changed, in this case when AppOverview response has returned:

@OverridepublicvoidonUpdate(App.AppDatadata, App.DataTypedataType){switch (dataType){caseAppOverview: Spinnerspinner = (Spinner)getActivity().findViewById(R.id.spinnerCategory); ArrayList<String> categories = map(data.getAppOverviewResponse().getAllTiers(), newFunction<Option, String>(){@OverridepublicStringapply(Optionoption){returnoption.getTitle()} }); spinner.setAdapter(newArrayAdapter<>(getActivity(), android.R.layout.simple_spinner_item, categories)); ListViewlist = (ListView)getActivity().findViewById(R.id.listTopRated); ArrayList<String> topTechnologyNames = map(getTopTechnologies(data), newFunction<TechnologyInfo, String>(){@OverridepublicStringapply(TechnologyInfotechnologyInfo){returntechnologyInfo.getName() + " (" + technologyInfo.getStacksCount() + ")"} }); list.setAdapter(newArrayAdapter<>(getActivity(), android.R.layout.simple_list_item_1, topTechnologyNames)); break} }

In this case the MainActivity home screen re-populates the Technology Category Spinner (aka Picker) and the Top Technologies ListView controls by assigning a new Android ArrayAdapter.

Functional Java Utils

The above example also introduces the map() functional util we've also included in the net.servicestack:client dependency to allow usage of Functional Programming techniques to transform, query and filter data given Android's Java 7 lack of any language or library support for Functional Programming itself. Unfortunately lack of closures in Java forces more boilerplate than otherwise would be necessary as it needs to fallback to use anonymous Type classes to capture delegates. Android Studio also recognizes this pattern as unnecessary noise and will automatically collapse the code into a readable closure syntax, with what the code would've looked like had Java supported closures, e.g:

Android Studio Collapsed Closure

The Func.java static class contains a number of common functional API's providing a cleaner and more robust alternative to working with Data than equivalent imperative code. We can take advantage of static imports in Java to import the namespace of all utils with the single import statement below:

importstaticnet.servicestack.client.Func.*;

Which will let you reference all the Functional utils below without a Class prefix:

ArrayList<R> map(Iterable<T> xs, Function<T,R> f) ArrayList<T> filter(Iterable<T> xs, Predicate<T> predicate) voideach(Iterable<T> xs, Each<T> f) Tfirst(Iterable<T> xs) Tfirst(Iterable<T> xs, Predicate<T> predicate) Tlast(Iterable<T> xs) Tlast(Iterable<T> xs, Predicate<T> predicate) booleancontains(Iterable<T> xs, Predicate<T> predicate) ArrayList<T> skip(Iterable<T> xs, intskip) ArrayList<T> skip(Iterable<T> xs, Predicate<T> predicate) ArrayList<T> take(Iterable<T> xs, inttake) ArrayList<T> take(Iterable<T> xs, Predicate<T> predicate) booleanany(Iterable<T> xs, Predicate<T> predicate) booleanall(Iterable<T> xs, Predicate<T> predicate) ArrayList<T> expand(Iterable<T>... xss) TelementAt(Iterable<T> xs, intindex) ArrayList<T> reverse(Iterable<T> xs) reduce(Iterable<T> xs, EinitialValue, Reducer<T,E> reducer) EreduceRight(Iterable<T> xs, EinitialValue, Reducer<T,E> reducer) Stringjoin(Iterable<T> xs, Stringseparator) ArrayList<T> toList(Iterable<T> xs)

Images and Custom Binary Requests

The TechStacks Android App also takes advantage of the Custom Service Client API's to download images asynchronously. As images can be fairly resource and bandwidth intensive they're stored in a simple Dictionary Cache to minimize any unnecessary CPU and network resources, i.e:

HashMap<String,Bitmap> imgCache = newHashMap<>(); publicvoidloadImage(finalStringimgUrl, finalImageResultcallback){Bitmapimg = imgCache.get(imgUrl); if (img != null){callback.success(img); return} client.getAsync(imgUrl, newAsyncResult<byte[]>(){@Overridepublicvoidsuccess(byte[] imgBytes){Bitmapimg = AndroidUtils.readBitmap(imgBytes); imgCache.put(imgUrl, img); callback.success(img)} })}

The TechStacks App uses the above API to download screenshots and load their Bitmaps in ImageView UI Controls, e.g:

StringimgUrl = result.getScreenshotUrl(); finalImageViewimg = (ImageView)findViewById(R.id.imgTechStackScreenshotUrl); data.loadImage(imgUrl, newApp.ImageResult(){@Overridepublicvoidsuccess(Bitmapresponse){img.setImageBitmap(response)} });

About

TechStacks Mobile Android App

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •  

Languages