{"id":3724,"url":"https://github.com/pocorall/scaloid","last_synced_at":"2025-04-11T06:21:59.136Z","repository":{"id":4962619,"uuid":"6120146","full_name":"pocorall/scaloid","owner":"pocorall","description":"Scaloid makes your Android code easy to understand and maintain.","archived":false,"fork":false,"pushed_at":"2024-06-13T04:46:50.000Z","size":4079,"stargazers_count":2091,"open_issues_count":17,"forks_count":161,"subscribers_count":137,"default_branch":"master","last_synced_at":"2025-04-03T06:07:53.539Z","etag":null,"topics":["scala"],"latest_commit_sha":null,"homepage":"","language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pocorall.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2012-10-08T05:53:22.000Z","updated_at":"2025-03-21T03:42:06.000Z","dependencies_parsed_at":"2024-09-24T22:10:20.724Z","dependency_job_id":"9a7e2a25-6996-47ae-a150-b95e82bd80fc","html_url":"https://github.com/pocorall/scaloid","commit_stats":{"total_commits":833,"total_committers":19,"mean_commits":43.8421052631579,"dds":"0.16326530612244894","last_synced_commit":"e2967434b2d394f9c71c81fcc53018ee05601712"},"previous_names":[],"tags_count":31,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pocorall%2Fscaloid","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pocorall%2Fscaloid/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pocorall%2Fscaloid/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pocorall%2Fscaloid/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pocorall","download_url":"https://codeload.github.com/pocorall/scaloid/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248352463,"owners_count":21089453,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["scala"],"created_at":"2024-01-05T20:16:49.623Z","updated_at":"2025-04-11T06:21:59.104Z","avatar_url":"https://github.com/pocorall.png","language":"Scala","funding_links":[],"categories":["Table of Contents","Development Alternatives","Android","[](https://github.com/JStumpp/awesome-android/blob/master/readme.md#development-alternatives)非Java开发安卓应用"],"sub_categories":["Android","Scala","[](https://github.com/JStumpp/awesome-android/blob/master/readme.md#scala)Scala"],"readme":"\n# Simpler Android\n\nScaloid is a library that simplifies your Android code. It makes your code easy to understand and maintain by [leveraging Scala language](https://github.com/pocorall/scaloid/wiki/Appendix#wiki-faqs-about-scaloid).\n\nFor example, the code block shown below:\n\n```scala\nval button = new Button(context)\nbutton.setText(\"Greet\")\nbutton.setOnClickListener(new OnClickListener() {\n  def onClick(v: View) {\n    Toast.makeText(context, \"Hello!\", Toast.LENGTH_SHORT).show()\n  }\n})\nlayout.addView(button)\n```\n\nis reduced to:\n\n```scala\nSButton(\"Greet\", toast(\"Hello!\"))\n```\n\n\n### Benefits\n * **Write elegant Android software**\u003cbr/\u003e\n   Simplicity is number one principle, keeps programmability and type-safety.\n * **Easy to use**\u003cbr/\u003e\n   Check the [quick start guide](https://github.com/pocorall/scaloid/wiki/Installation#wiki-quick-start)\n * **Compatible with your legacy code**\u003cbr/\u003e\n   You can [use both Scaloid and plain-old Java Android API](https://github.com/pocorall/scaloid/wiki/Appendix#wiki-i-cant-use-scaloid-because-it-does-not-provide-a-functionality-x). You can gradually improve your legacy code.\n * **Production quality**\u003cbr/\u003e\n   Not a toy project. The creator of Scaloid uses it to build [a millionth downloaded app](https://play.google.com/store/apps/details?id=com.soundcorset.client.android).\n\n### Demos\n\nFork one of this to start a new project:\n * [\u003cb\u003eHello world of Scaloid for sbt\u003c/b\u003e](https://github.com/pocorall/hello-scaloid-sbt) (recommended, it builds faster)\n * [\u003cb\u003eHello world of Scaloid for maven\u003c/b\u003e](https://github.com/pocorall/hello-scaloid-maven)\n * [\u003cb\u003eHello world of Scaloid for gradle\u003c/b\u003e](https://github.com/pocorall/hello-scaloid-gradle)\n\nLearn how Scaloid can be used in action:\n * [\u003cb\u003eScaloid port of apidemos app\u003c/b\u003e](https://github.com/pocorall/scaloid-apidemos)\n * [\u003cb\u003eList of projects using Scaloid\u003c/b\u003e](https://github.com/pocorall/scaloid/wiki/Appendix#wiki-list-of-projects-using-scaloid)\n * [\u003cb\u003eTutorial by Gaston Hillar\u003c/b\u003e](http://www.drdobbs.com/mobile/developing-android-apps-with-scala-and-s/240161584) - [part 1](http://www.drdobbs.com/mobile/developing-android-apps-with-scala-and-s/240161584) and [part 2](http://www.drdobbs.com/mobile/developing-android-apps-with-scala-and-s/240162204)\n\n\n## Contents\n\n * [Core design principle](#core-design-principle)\n * [UI Layout without XML](#ui-layout-without-xml)\n   * [Layout context](#layout-context)\n   * [Styles for programmers](#styles-for-programmers)\n   * [Automatic layout converter](#automatic-layout-converter)\n * [Lifecycle management](#lifecycle-management)\n * [Asynchronous task processing](https://github.com/pocorall/scaloid/wiki/Basics#wiki-asynchronous-task-processing)\n * [Implicit conversions](https://github.com/pocorall/scaloid/wiki/Basics#wiki-implicit-conversions)\n   * [Shorter listeners](https://github.com/pocorall/scaloid/wiki/Basics#wiki-enriched-implicit-classes)\n   * [Database cursor](http://blog.scaloid.org/2014/02/simple-enhancements-on-accessing.html)\n * [Traits](https://github.com/pocorall/scaloid/wiki/Basics#wiki-traits)\n * [Smarter logging](https://github.com/pocorall/scaloid/wiki/Basics#wiki-logging)\n * [Improved getters/setters](#scala-getters-and-setters)\n * [Classes](#classes)\n   * [Concise dialog builder](#class-alertdialogbuilder)\n   * [Beauty ArrayAdapter](#class-sarrayadapter)\n   * [Dynamically Preferences](#class-preferences)  [\u003csub\u003e`Read in blog`\u003c/sub\u003e](http://blog.scaloid.org/2013/03/dynamicly-accessing-sharedpreferences.html)\n   * [Binding services concisely](#class-localservice)  [\u003csub\u003e`Read in blog`\u003c/sub\u003e](http://blog.scaloid.org/2013/03/introducing-localservice.html)\n   \n## Other links   \n * [\u003cb\u003eQuick start guide\u003c/b\u003e](https://github.com/pocorall/scaloid/wiki/Installation#wiki-quick-start)\n * [\u003cb\u003eAPI doc\u003c/b\u003e](http://docs.scaloid.org/)\n * [\u003cb\u003eBlog\u003c/b\u003e](http://blog.scaloid.org/)\n * [\u003cb\u003eTwitter\u003c/b\u003e](https://twitter.com/scaloid/)\n * [\u003cb\u003eFAQs\u003c/b\u003e](https://github.com/pocorall/scaloid/wiki/Appendix#wiki-faqs-about-scaloid)\n     * [FAQs about Scala on Android](https://github.com/pocorall/scaloid/wiki/Appendix#wiki-faqs-about-scala-on-android)\n * [\u003cb\u003eInside Scaloid\u003c/b\u003e](https://github.com/pocorall/scaloid/wiki/Inside-Scaloid)\n * [\u003cb\u003eWe are hiring!\u003c/b\u003e](#we-are-hiring)\n\n## Core design principle\n\n\"Being practically simple\" is number one principle of Scaloid. Most frequently used things should be written shorter, like [Huffman coding](https://en.wikipedia.org/wiki/Huffman_coding). To do this, I first observed Android programs I wrote, and thought that which part of the code is more fundamental than others. For example, what is the most essential part of buttons? Buttons should have some visible things on it, such as title or image, so the buttons are created like this: `SButton(\"Hello\")`. The second essential part is doing something when it is pressed: `SImageButton(R.drawable.hello, toast(\"World!\"))`. What should be the third one? The answer might not the same for every people, but I think that repetition frequency of press-and-hold action is nice: `SButton(\"Add\", n += 1, 500)` increases `n` for every 500 milliseconds when the user holds the button.\n\n## UI Layout without XML\n\u003cp align=\"center\"\u003e\u003cimg src=\"http://o-n2.com/verboseSimple.png\"\u003e\u003c/p\u003e\n\nAndroid SDK leverages XML to build UI layouts. However, XML is considered still a bit verbose, and lacks programmability. Scaloid composes UI layout in Scala DSL style, therefore achieve both clarity and programmability. For example, suppose a legacy XML layout as shown below:\n\n```xml\n\u003cLinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:orientation=\"vertical\" android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\" android:padding=\"20dip\"\u003e\n    \u003cTextView android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\" android:text=\"Sign in\"\n            android:layout_marginBottom=\"25dip\" android:textSize=\"24.5sp\"/\u003e\n    \u003cTextView android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\" android:text=\"ID\"/\u003e\n    \u003cEditText android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\" android:id=\"@+id/userId\"/\u003e\n    \u003cTextView android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\" android:text=\"Password\"/\u003e\n    \u003cEditText android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\" android:id=\"@+id/password\"\n            android:inputType=\"textPassword\"/\u003e\n    \u003cButton android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\" android:id=\"@+id/signin\"\n            android:text=\"Sign in\"/\u003e\n    \u003cLinearLayout android:orientation=\"horizontal\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\u003e\n        \u003cButton android:text=\"Help\" android:id=\"@+id/help\"\n                android:layout_width=\"match_parent\" android:layout_height=\"wrap_content\"/\u003e\n        \u003cButton android:text=\"Sign up\" android:id=\"@+id/signup\"\n                android:layout_width=\"match_parent\" android:layout_height=\"wrap_content\"/\u003e\n    \u003c/LinearLayout\u003e\n\u003c/LinearLayout\u003e\n```\n\nis reduced to:\n\n```scala\nnew SVerticalLayout {\n  STextView(\"Sign in\").textSize(24.5.sp).\u003c\u003c.marginBottom(25.dip).\u003e\u003e\n  STextView(\"ID\")\n  SEditText()\n  STextView(\"Password\")\n  SEditText() inputType TEXT_PASSWORD\n  SButton(\"Sign in\")\n  new SLinearLayout {\n    SButton(\"Help\")\n    SButton(\"Sign up\")\n  }.wrap.here\n}.padding(20.dip)\n```\n\nThe layout description shown above is highly programmable. You can easily wire your logic into the layout:\n\n```scala\nnew SVerticalLayout {\n  STextView(\"Sign in\").textSize(24.5.sp).\u003c\u003c.marginBottom(25.dip).\u003e\u003e\n  STextView(\"ID\")\n  val userId = SEditText()\n  STextView(\"Password\")\n  val pass = SEditText() inputType TEXT_PASSWORD\n  SButton(\"Sign in\", signin(userId.text, pass.text))\n  new SLinearLayout {\n    SButton(\"Help\", openUri(\"http://help.url\"))\n    SButton(\"Sign up\", openUri(\"http://signup.uri\"))\n  }.wrap.here\n}.padding(20.dip)\n```\n\nBecause a Scaloid layout description is plain Scala code, it is type-safe.\n\n### Automatic layout converter\n\nThis converter turns an Android XML layout into a Scaloid layout:\n\nhttp://layout.scaloid.org\n\n### Migration tip \n\nScaloid is fully compatible with legacy xml layout files. \nYou can access a widget described in xml layout as:\n\n```scala\nonCreate {\n  setContentView(R.layout.main)\n  val name = find[EditText](R.id.name)\n  // do something with `name`\n}\n```\n\n### Responsive layout\n\nBasically, a layout written in Scaloid is just an ordinary Scala code, so you can just freely composite the layout according to the device configuration:\n\n```scala\nimport org.scaloid.util.Configuration._\n\nif(long) SButton(\"This button is shown only for a long screen \"\n  + \"dimension (\"+ width + \", \" + height + \")\")\nif(landscape) new SLinearLayout {\n  SButton(\"Buttons for\")\n  SButton(\"landscape layout\")\n  if(dpi \u003c= HDPI) SButton(\"You have a high resolution display!\")\n}.here\n```\n\nPlease refer to this blog post for more detail:\n - [Syntactic sugar for multiple device configuration](http://blog.scaloid.org/2013/08/syntactic-sugar-for-multiple-device.html)\n\n### Further readings about Scaloid layout \n\n - [Accessing widgets in view class](http://blog.scaloid.org/2013/04/accessing-widgets-in-view-classes.html)\n - [Layout context](#layout-context)\n - [In-depth tutorial on styles](http://blog.scaloid.org/2013/01/a-css-like-styling-on-android.html)\n - [Styles for programmers](#styles-for-programmers)\n\n## Lifecycle management\n\nWith Android API, Registering and unregistering BroadcastReceiver can be done as:\n\n```scala\nvar connectivityListener: BroadcastReceiver = null\n\ndef onResume() {\n  super.onResume()\n  // ...\n  connectivityListener = new BroadcastReceiver {\n    def onReceive(context: Context, intent: Intent) {\n     doSomething()\n    }\n  } \n  registerReceiver(connectivityListener, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION))\n}\n\ndef onPause() {\n  unregisterReceiver(connectivityListener)\n  // ...\n  super.onPause()\n}\n```\n\nIn Scaloid, the directly equivalent code is:\n\n```scala\nbroadcastReceiver(ConnectivityManager.CONNECTIVITY_ACTION) {\n  doSomething()\n}\n```\n\nScaloid has highly flexible resource register/unregister management architecture.\nIf this code is written in services, registering and unregistering is done in onCreate and onDestroy respectively. \nIf the same code is in activities, registering and unregistering is done in onResume and onPause respectively.\nThis is just a default behavior. \nYou can override a preference that determine when the register/unregister preforms. \nOverriding it is simple as well:\n\n```scala\nbroadcastReceiver(ConnectivityManager.CONNECTIVITY_ACTION) {\n  doSomething()\n}(this, onStartStop)\n```\n\nThen, the receiver is registered onStart, and unregistered onStop.\n\n#### onDestroy can be called in many times!\n\nYou can declare `onDestroy` behaviors in many places. This simplifies resource management significantly. Suppose you want to open a stream from a file: \n\n```scala\ndef openFileStream(file: File): InputStream = {\n  val stream = new FileInputStream(file)\n  onDestroy(stream.close()) // automatically closed when the Activity is destroyed!!\n  stream\n}\n```\n\n`onDestroy` is a method that adds a function into the job list triggered when the activity is destroyed. So, we just get a stream from `openFileStream()` and forget about releasing it.\nOther lifecycle states (`onCreate`, `onResume`, `onStop` and so on) can be treated in the same way.\n\n**Further reading:** Refer to [this blog post](http://blog.scaloid.org/2013/02/better-resource-releasing-in-android.html) for more details.\n \n \n## Asynchronous task processing\n\nAndroid API provides `runOnUiThread()` only for class `Activity`. Scaloid provides a Scala version of `runOnUiThread()` for anywhere other than `Activity`.\n\nInstead of:\n\n```scala\nactivity.runOnUiThread {\n  new Runnable() {\n    def run() {\n      debug(\"Running only in Activity class\")\n    }\n  }\n}\n```\n\nIn Scaloid, use it like this:\n\n```scala\nrunOnUiThread(debug(\"Running in any context\"))\n```\n\nRunning a job asynchronously and notifying the UI thread is a very frequently used pattern. Although Android API provides a helper class `AsyncTask`, implementing such a simple idea is still painful, even when we use Scala:\n\n```scala\nnew AsyncTask[String, Void, String] {\n  def doInBackground(params: Array[String]) = {\n    doAJobTakeSomeTime(params)\n  }\n\n  override def onPostExecute(result: String) {\n    alert(\"Done!\", result)\n  }\n}.execute(\"param\")\n```\n\nUsing [`scala.concurrent.Future`](http://docs.scala-lang.org/sips/completed/futures-promises.html), the asynchronous job shown above can be rewritten like this:\n\n```scala\nFuture {\n  val result = doAJobTakeSomeTime(params)\n  runOnUiThread(alert(\"Done!\", result))\n}\n```\n\nWhen you don't want to build sophisticate UI interactions, but just want to display something by calling a single Scaloid method (e.g. `alert`, `toast`, and `spinnerDialog`), Scaloid handles `runOnUiThread` for you. Therefore, the code block shown above is reduced to:\n\n```scala\nFuture {\n  alert(\"Done!\", doAJobTakeSomeTime(params))\n}\n```\n\nIt is a great win as it exposes your idea clearly.\n\nJust like we thrown away `AsyncTask`, we can also eliminate all other Java helpers for asynchronous job, such as `AsyncQueryHandler` and `AsyncTaskLoader`. Compare with the [original Java code](http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android-apps/4.1.1_r1/com/example/android/apis/view/ExpandableList2.java?av=h)\nand a [Scala port](https://github.com/pocorall/scaloid-apidemos/blob/master/src/main/java/com/example/android/apis/view/ExpandableList2.scala) of ApiDemos example app.\n\nUsing `Future` is just an example of asynchronous task processing in Scaloid. You can freely use any modern task management utilities.\n\n**Further reading:** Refer to [this blog post](http://blog.scaloid.org/2013/11/using-scalaconcurrentfuture-in-android.html) for an important consideration when using `Future` in Android.\n\n\n## Implicit conversions\nScaloid employs several implicit conversions. Some of the available implicit conversions are shown below:\n\n##### Uri conversion\n\n```scala\nString =\u003e Uri\n```\n\nThe functions such as [play ringtones](#play-ringtones) `play()` or [open URIs](#open-uris) `openUri()` takes an instance of `Uri` as a parameter. However, we frequently have URIs as a `String`. Scaloid implicitly converts `String` into `Uri`. Therefore, you can freely use `String` when you play a ringtone:\n\n```scala\nplay(\"content://media/internal/audio/media/50\")\n```\n\n, open a URI:\n\n```scala\nopenUri(\"http://scaloid.org\")\n```\n\n, or wherever you want.\n\nAlternatively, you can specify the conversion as:\n\n```scala\nval uri:Uri = \"http://scaloid.org\".toUri\n```\n\n##### Unit conversion\n\nUnits `dip` and `sp` can be converted into the pixel unit.\n\n```scala\nval inPixel:Int = 32.dip\nval inPixel2:Int = 22.sp\n```\n\nReversely, pixel unit can also be converted into `dip` and `sp` unit.\n\n```scala\nval inDip:Double = 35.px2dip\nval inSp:Double = 27.px2sp\n```\n\n##### Resource IDs\n\nScaloid provides several implicit conversions that convert from `Int` type resource ID to `CharSequence`, `Array[CharSequence]`, `Array[String]`, `Drawable` and `Movie`.\nFor example:\n\n```scala\ndef toast(msg:CharSequence) = ...\n\ntoast(R.string.my_message) // implicit conversion works!\n```\n\nAlthough Scaloid provides these conversions implicitly, explicit conversion may be required in some context. In this case, methods `r2...` are provided for the `Int` type:\n\n```scala\nwarn(\"Will display the content of the resource: \" + R.string.my_message.r2String)\n```\n\nCurrently, `r2Text`, `r2TextArray`, `r2String`, `r2StringArray`, `r2Drawable` and `r2Movie` is provided.\n\n\n**Further reading:**\n * [Why implicit conversion of Resource ID is cool?](https://github.com/pocorall/scaloid/wiki/Basics#wiki-why-implicit-conversion-of-resource-id-is-cool)\n\n\n\n## Context as an implicit parameter\nMany methods in the Android API require an instance of a class `Context`. Providing this for every method call results in clumsy code. We employ an implicit parameter to eliminate this. Just declare an implicit value that represents current context:\n\n```scala\nimplicit val ctx = ...\n```\n\nor just extend trait `SContext`, which defines it for you. Then the code that required `Context` becomes much simpler, for example:\n\n\n##### Intent\n\n```scala\nnew Intent(context, classOf[MyActivity])\n```\n\nis reduced to:\n\n```scala\nSIntent[MyActivity]\n```\n\nWhen a method takes an `Intent` as a first parameter in which we want to pass the newly created intent object, the parameter can be omitted. For example:\n\n```scala\nstartService(new Intent(context, classOf[MyService]))\nstopService(new Intent(context, classOf[MyService]))\n```\n\nis reduced to:\n\n```scala\nstartService[MyService]\nstopService[MyService]\n```\n\nor\n\n```scala\nval intent = // initialize the intent and put some attributes on it\nintent.start[MyActivity]\n```\n\nAn intent that has a long list of extra attributes:\n\n```scala\nnew Intent().putExtra(\"valueA\", valueA).putExtra(\"valueB\", valueB).putExtra(\"valueC\", valueC)\n```\n\nis reduced to:\n\n```scala\nnew Intent().put(valueA, valueB, valueC)\n```\n\n##### Toast\n\n```scala\ntoast(\"hi, there!\")\n```\n\nIf you want a longer toast:\n\n```scala\nlongToast(\"long toast\")\n```\n\n##### Dialog\n\n```scala\nProgressDialog.show(context, \"Dialog\", \"working...\", true)\n```\n\nis reduced to:\n\n```scala\nspinnerDialog(\"Dialog\", \"working...\")\n```\n\nWhen you call `toast`, `longToast` or `spinnerDialog` from non-UI thread, you [don't have to mind about threading](#asynchronous-task-processing).\nThe toast example shown above is equivalent to the following Java code: \n\n```java\nactivity.runOnUiThread(new Runnable() {\n    public void run() {\n        Toast.makeText(activity, \"hi, there!\", Toast.LENGTH_SHORT).show();\n    }\n});\n```\n\n##### Pending intent\n\n```scala\nPendingIntent.getActivity(context, 0, new Intent(context, classOf[MyActivity]), 0)\nPendingIntent.getService(context, 0, new Intent(context, classOf[MyService]), 0)\n```\n\nis reduced to:\n\n```scala\npendingActivity[MyActivity]\npendingService[MyService]\n```\n\n##### Open URIs\n\nThis opens a web browser (or another view assigned to the http protocol).\n\n```scala\nopenUri(\"http://scaloid.org\")\n```\n\n\n##### System services\n\nGetting system service objects become much simpler. The following legacy code:\n\n```scala\nval vibrator = context.getSystemService(Context.VIBRATOR_SERVICE).asInstanceOf[Vibrator]\nvibrator.vibrate(500)\n```\n\nis reduced to:\n\n```scala\nvibrator.vibrate(500)\n```\n\nUnder the hood, Scaloid defines a function `vibrator` like this:\n\n```scala\ndef vibrator(implicit ctx: Context) = ctx.getSystemService(Context.VIBRATOR_SERVICE).asInstanceOf[Vibrator]\n```\n\nAll the system service accessors available in Android API level 8 are defined (e.g. `audioManager`, `alarmManager`, `notificationManager`, etc.). The name of a system service accessor is the same as its class name, except that the first character is lowercased.\n\n\n## Enriched Implicit classes\n\nSuppose an Android class `Foo`, for example, Scaloid defines an implicit conversion `Foo =\u003e RichFoo`. The class `RichFoo` defines additional methods for more convenient access to `Foo`. This is a common pattern in Scala to extend existing API (see [pimp-my-library](http://www.artima.com/weblogs/viewpost.jsp?thread=179766) pattern). This section describes various features added on existing Android API classes.\n\n\n##### Listeners\n\nAndroid API defines many listener interfaces for callback notifications. For example, `View.OnClickListener` is used to be notified when a view is clicked:\n\n```scala\nfind[Button](R.id.search).setOnClickListener(new View.OnClickListener {\n  def onClick(v:View) {\n    openUri(\"http://scaloid.org\")\n  }\n})\n```\n\nScaloid provides a shortcut that dramatically reduces the length of the code:\n\n```scala\nfind[Button](R.id.search).onClick(openUri(\"http://scaloid.org\"))\n```\n\nAll other listener-appending methods such as `.onKey()`, `.onLongClick()`, and `.onTouch()` are defined.\n\nSome conventions we employed for method naming are:\n\n * We omit `set...`, `add...`, and `...Listener` from the method name, which is less significant.\u003cbr/\u003e\n   For example, `.setOnKeyListener()` becomes `.onKey()`.\n * Every method has two versions of parameters overridden. One is a lazy parameter, and another is a function which has full parameters defined in the original Android API. For example, these two usages are valid:\n\n```scala\nbutton.onClick(info(\"touched\"))\nbutton.onClick((v:View) =\u003e info(\"touched a button \"+v))\n```\n\n * Methods `add...` is abbreviated with a method `+=` if it is not a listener-appender.\u003cbr/\u003e\n   For example, `layout.addView(button)` becomes `layout += button`.\n\n##### Multiple method listeners\n\nMethods `beforeTextChanged()`, `onTextChanged()`, and `afterTextChanged()` are defined in `RichTextView`, which can be implicitly converted from `TextView`. It is more convenient than using `TextWatcher` directly. For example:\n\n```scala\ninputField.beforeTextChanged(saveTextStatus())\n```\n\nis equivalent to:\n\n```scala\ninputField.addTextChangedListener(new TextWatcher {\n  def beforeTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {\n    saveTextStatus()\n  }\n\n  def onTextChanged(p1: CharSequence, p2: Int, p3: Int, p4: Int) {}\n\n  def afterTextChanged(p1: Editable) {}\n})\n```\n\nAlso, we override `beforeTextChanged()` with full parameters defined in the original listener:\n\n```scala\ninputField.beforeTextChanged((s:CharSequence, _:Int, _:Int) =\u003e saveText(s))\n```\n\nOther listeners in Android API can also be accessed in this way.\n\n## Layout context\n\nIn Android API, layout information is stored into a `View` object via the method `View.setLayoutParams(ViewGroup.LayoutParams)`. A specific type of parameter passing into that method is determined by a the type of `...Layout` object which contains the `View` object. For example, let us see some Java code shown below:\n\n```java\nLinearLayout layout = new LinearLayout(context);\nButton button = new Button(context);\nbutton.setText(\"Click\");\nLinearLayout.LayoutParams params = new LinearLayout.LayoutParams();\nparams.weight = 1.0f;  // sets some value\nbutton.setLayoutParams(params);\nlayout.addView(button);\n```\n\nBecause the button is appended into the `LinearLayout`, the layout parameter must be `LinearLayout.LayoutParams`, otherwise a ___runtime error___ might be occurred. Meanwhile, Scaloid eliminate this burden, while still preserving rigorous typing of `LayoutParams`. The code shown below is equivalent to the previous Java code:\n\n```scala\nval layout = new SLinearLayout {\n  SButton(\"Click\").\u003c\u003c.Weight(1.0f).\u003e\u003e\n}\n```\n\nIn the anonymous constructor of 'SLinearLayout', Scaloid provides an implicit function called \"layout context\". This affects a return type of the method `\u003c\u003c` defined in the class `SButton`. \nIf we use `SFrameLayout` as a layout context, the method `\u003c\u003c` returns `FrameLayout.LayoutParams`, which does not have `Weight` method. Therefore, the code below results a ___syntax error___.\n\n```scala\nval layout = new SFrameLayout {\n  SButton(\"Click\").\u003c\u003c.Weight(1.0f).\u003e\u003e   // Syntax error on Weight()\n}\n```\n\nCompared with XML layout description, Scaloid layout is simple and type-safe.\n\nThe method `\u003c\u003c` is overloaded with parameters `\u003c\u003c(width:Int, height:Int)` which assigns the size of the view component. For example:\n\n```scala\nSButton(\"Click\").\u003c\u003c(40.dip, WRAP_CONTENT)\n```\n\n#### Operator `new` and method `apply`\n\nUsually, `View` components are referenced multiple times in an `Activity`. For example:\n\n```scala\nlazy val button = new SButton() text \"Click\"\nonCreate {\n  contentView = new SLinearLayout {\n    button.here\n  }\n}\n// ... uses the button somewhere in other methods (e.g. changing text or adding listeners)\n```\n\n[Prefixed classes](https://github.com/pocorall/scaloid/wiki/Basics#wiki-prefixed-classes) in Scaloid (e.g. `SButton`) have a companion object that implements `apply` methods that create a new component. These methods also append the component to the layout context that enclose the component. \nTherefore, the code block from the above example:\n\n```scala\nbutton = new SButton() text \"Click\"\nbutton.here\n```\n\nis equivalent to:\n\n```scala\nbutton = SButton(\"Click\")\n```\n\nBecause the `apply` methods access to the layout context, it cannot be called outside of the layout context. \nIn this case, use the `new` operator instead.\n\n#### Method `\u003e\u003e`\n\nAs we noted, the method `\u003c\u003c` returns an object which is a type of `ViewGroup.LayoutParams`:\n\n```scala\nval params = SButton(\"Click\").\u003c\u003c   // type LayoutParams\n```\n\nThis class provides some setters for chaining:\n\n```scala\nval params = SButton(\"Click\").\u003c\u003c.marginBottom(100).marginLeft(10)   // type LayoutParams\n```\n\nif we want use the `SButton` object again, Scaloid provides `\u003e\u003e` method returning back to the object:\n\n```scala\nval button = SButton(\"Click\").\u003c\u003c.marginBottom(100).marginLeft(10).\u003e\u003e   // type SButton\n```\n\n#### Nested layout context\n\nWhen the layout context is nested, inner-most layout's context is applied:\n\n```scala\nval layout = new SFrameLayout {\n  new SLinearLayout {\n    SButton(\"Click\").\u003c\u003c.Weight(1.0f).\u003e\u003e   // in context of SLinearLayout\n  }.here\n}\n```\n\n#### Methods `fill`, `wrap`, `wf` and `fw`\n\nWhen we get a `LayoutParams` from `\u003c\u003c`, the default values of `width` and `height` properties are `width = FILL_PARENT` and `height = WRAP_CONTENT`. You can override this when you need it:\n\n```scala\nSButton(\"Click\").\u003c\u003c(FILL_PARENT, FILL_PARENT)\n```\n\nThis is a very frequently used idiom. Therefore we provide further shorthand:\n\n```scala\nSButton(\"Click\").\u003c\u003c.fill\n```\n\nIf you want the `View` element to be wrapped,\n\n```scala\nSButton(\"Click\").\u003c\u003c(WRAP_CONTENT, WRAP_CONTENT)\n```\n\nThis is also shortened as:\n\n```scala\nSButton(\"Click\").\u003c\u003c.wrap\n```\n\nSimilarly, `\u003c\u003c(WRAP_CONTENT, FILL_PARENT)` and `\u003c\u003c(FILL_PARENT, WRAP_CONTENT)` can also be shortend as `\u003c\u003c.wf` and `\u003c\u003c.fw` respectively.\n\nBecause there are so many occurences `\u003c\u003c.wrap.\u003e\u003e` pattern in actual Android code, it is allowed to remove `.\u003c\u003c` and `.\u003e\u003e` in this case:\n\n```scala\nSButton(\"Click\").wrap    // returns SButton type\n```\n\nThis pattern also usable for `.fill`, `.fw` and `.wf` methods.\n\n\n## Styles for programmers\n\n#### Naming conventions\n\nScaloid follows the naming conventions of XML attributes in the Android API with some improvements.\n\nFor XML attributes, layout related properties are prefixed with `layout_` and as you might have guessed, Scaloid does not need it. \nFor boolean attributes, the default is `false`. However, Scaloid flags it as `true` when the attribute is declared explicitly without any parameter. \nFor example:\n\n```scala\nnew SRelativeLayout {\n  STextView(\"hello\").\u003c\u003c.centerHorizontal.alignParentBottom.\u003e\u003e\n}\n```\n\nScaloid omits unnecessary `=\"true\"` for the attribute `centerHorizontal`. Equivalent XML layout description for `TextView` is:\n\n```xml\n\u003cTextView\n    android:id=\"@+id/helloText\"\n    android:layout_width=\"fill_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_centerHorizontal=\"true\"\n    android:layout_alignParentBottom=\"true\"\n    android:text=\"hello\"/\u003e\n```\n\nFor layout methods named with four directions (e.g. `...Top`, `...Right`, `...Bottom` and `...Left`), Scaloid provides additional methods that specifies all properties at once. For example, Because Android XML layout defines `margin...` properties(`marginTop(v:Int)`, `marginRight(v:Int)`, `marginBottom(v:Int)` and `marginLeft(v:Int)`), Scaloid provides additional `margin(top:Int, right:Int, bottom:Int, left:Int)` and `margin(amount:Int)` methods that can be used as:\n\n```scala\nSTextView(\"hello\").\u003c\u003c.margin(5.dip, 10.dip, 5.dip, 10.dip)\n```\n\nor\n\n```scala\nSTextView(\"hello\").\u003c\u003c.margin(10.sp)  // assigns the same value for all directions\n```\n\n\nAndroid SDK introduced [styles](http://developer.android.com/guide/topics/ui/themes.html) to reuse common properties on XML layout.\nWe repeatedly pointed out that XML is verbose.\nTo apply styles in Scaloid, you do not need to learn any syntax or API library, because Scaloid layout is an ordinary Scala code. Just write a code that work as styles.\n\n#### Basic: Assign it individually\n\nSuppose the following code that repeats some properties:\n\n```scala\nSButton(\"first\").textSize(20.dip).\u003c\u003c.margin(5.dip).\u003e\u003e\nSButton(\"prev\").textSize(20.dip).\u003c\u003c.margin(5.dip).\u003e\u003e\nSButton(\"next\").textSize(20.dip).\u003c\u003c.margin(5.dip).\u003e\u003e\nSButton(\"last\").textSize(20.dip).\u003c\u003c.margin(5.dip).\u003e\u003e\n```\n\nThen we can define a function that applies these properties:\n\n```scala\ndef myStyle = (_: SButton).textSize(20.dip).\u003c\u003c.margin(5.dip).\u003e\u003e\nmyStyle(SButton(\"first\"))\nmyStyle(SButton(\"prev\"))\nmyStyle(SButton(\"next\"))\nmyStyle(SButton(\"last\"))\n```\n\nStill not satisfying? Here we have a shorter one:\n\n```scala\ndef myStyle = (_: SButton).textSize(20.dip).\u003c\u003c.margin(5.dip).\u003e\u003e\nList(\"first\", \"prev\", \"next\", \"last\").foreach(title =\u003e myStyle(SButton(title)))\n```\n\n#### Advanced: CSS-like stylesheet\n\nScaloid provides `SViewGroup.style(View =\u003e View)` method to provide more generic component styling. \nThe parameter is a function which receives a view requested for styling, and returns a view which is finished applying the style.\nThen the example in the previous subsection becomes:\n\n```scala\nstyle {\n  case b: SButton =\u003e b.textSize(20.dip).\u003c\u003c.margin(5.dip).\u003e\u003e\n}\n\nSButton(\"first\")\nSButton(\"prev\")\nSButton(\"next\")\nSButton(\"last\")\n```\n\nNote that individually applying `myStyle` is reduced. Let us see another example:\n\n```scala\nstyle {\n  case b: SButton =\u003e b.textColor(Color.RED).onClick(toast(\"Bang!\"))\n  case t: STextView =\u003e t.textSize(10.dip)\n  case v =\u003e v.backgroundColor(Color.YELLOW)\n}\n\nSTextView(\"I am 10.dip tall\")\nSTextView(\"Me too\")\nSTextView(\"I am taller than you\").textSize(15.dip) // overriding\nSEditText(\"Yellow input field\")\nSButton(\"Red alert!\")\n```\n  \nSimilar to CSS, you can assign different styles for each classes using Scala pattern matching. \nUnlike Android XML styles or even CSS, Scaloid can assign some actions to the component (see `onclick(toast(...))`), or can do anything that you imagine. \nAlso, you can easily override the property individually, as shown in the example above.\n\nLast thing that you may missed: These are type-safe. If you made a mistake, compiler will check it for you.\n\n**Further readings:**\n\n - [Accessing widgets in view class](http://blog.scaloid.org/2013/04/accessing-widgets-in-view-classes.html)\n - [In-depth tutorial on styles](http://blog.scaloid.org/2013/01/a-css-like-styling-on-android.html)\n  \n## Traits\n\n### Trait `UnregisterReceiver`\n\nWhen you register `BroadcastReceiver` with `Context.registerReceiver()` you have to unregister it to prevent memory leak. Trait `UnregisterReceiver` handles these chores for you. All you need to do is append the trait to your class.\n\n```scala\nclass MyService extends SService with UnregisterReceiver {\n  def func() {\n    // ...\n    registerReceiver(receiver, intentFilter)\n    // Done! automatically unregistered at UnregisterReceiverService.onDestroy()\n  }\n}\n```\n\n### Trait `SActivity`\n\nInstead of\n\n```scala\nfindViewById(R.id.login).asInstanceOf[Button]\n```\n\nuse a shorthand:\n\n```scala\nfind[Button](R.id.login)\n```\n\nAlthough we provide this shorthand, Scaloid recommends [programmatically laying out UI, not with XML](https://github.com/pocorall/scaloid/wiki/UI-Layout-without-XML).\n\n## Activity as an implicit parameter\nSimilar to the [implicit context](#context-as-an-implicit-parameter), an `Activity` typed implicit parameter is also required for some methods. Therefore, you have to define an activity as an implicit value:\n\n```scala\nimplicit val ctx: Activity = ...\n```\n\nBecause the class `Activity` is a subclass of `Context`, it can also be an implicit context.\nWhen you extend `SActivity`, object `this` is assigned as the implicit activity by default.\n\nHere we show some example cases of using the implicit activity:\n\n#### Automatically allocate a unique `View` ID\n\nOften, `Views` are required to have an ID value. Although Android API document specifies that the ID need not be unique, allocating unique ID is virtually mandatory in practice. Scaloid provides a package scope function `getUniqueId`, which returns `Int` type ID that is not allocated by any existing `View` components for given implicit activity.\n\n```scala\nval newUniqueIdForCurrentActivity = getUniqueId\n```\n\nUsing this, Scaloid also extended `View` class to add a method `uniqueId`, that assigns a new unique ID if it is not already allocated. \n\n```scala\nval uniqueIdOfMyView = myView.uniqueId\n```\n\nOne of the good use case of `uniqueId` is `SRelativeLayout`. Some of the methods in this layout context, such as `below`, `above`, `leftOf` and `rightOf`, takes another `View` object as an anchor:\n\n```scala\nnew SRelativeLayout {\n  val btn = SButton(R.string.hi)\n  SButton(\"There\").\u003c\u003c.below(btn)\n}\n```\n\nHere we show the implementation of the `below` function:\n\n```scala\ndef below(anchor: View)(implicit activity: Activity) = {\n  addRule(RelativeLayout.BELOW, anchor.uniqueId)\n  this\n}\n```\n\nA new unique ID is assigned to the `anchor` if it is not assigned already, and passes it to `addRule` function. \n\n## Logging\n\nUnlike other logging frameworks, Android Logging API requires a `String` tag for every log call. We eliminate this by introducing an implicit parameter. Define an implicit value type of `LoggerTag` as shown:\n\n```scala\nimplicit val loggerTag = LoggerTag(\"MyAppTag\")\n```\n\nor, extend trait `TagUtil` or `SContext` which defines the tag by default. Then you can simply log like this:\n\n```scala\nwarn(\"Something happened!\")\n```\n\nOther functions for every log level (`verbose()`, `debug()`, `info()`, `warn()`, `error()` and `wtf()`) are available.\n\n```scala\ninfo(\"hello \" + world)\n```\n\nA `String` parameter passed with `info()` is a by-name parameter, so it is evaluated only if the logging is possible. Therefore, the example shown above is equivalent to:\n\n```scala\nval tag = \"MyAppTag\"\nif(Log.isLoggable(tag, Log.INFO)) Log.i(tag, \"hello \" + world)\n```\n\n\n## Scala getters and setters\n\nYou can use any of the setters listed below:\n\n* `obj.setText(\"Hello\")` Java bean style\n* `obj.text = \"Hello\"` Assignment style\n* `obj text \"Hello\"` DSL style\n* `obj.text(\"Hello\")` Method calling style\n\nCompared to Java style getters and setters, for example:\n\n```scala\nnew TextView(context) {\n  setText(\"Hello\")\n  setTextSize(15)\n}\n```\n\nthat of Scala style clearly reveals the nature of the operations as shown below:\n\n```scala\nnew STextView {\n  text = \"Hello\"\n  textSize = 15\n}\n```\n\nOr, you can also chain the setters:\n\n```scala\nnew STextView text \"Hello\" textSize 15\n```\n\nwhich is a syntactic sugar for:\n\n```scala\nnew STextView.text(\"Hello\").textSize(15)\n```\n\nWe recommend \"assignment style\" and \"DSL style\". Use assignment style when you emphasize that you are assigning something, or use DSL style when the code length of the assignee is short and needs to be chained.\n\nNote: Using `.apply(String)` method on object `STextView`, you can further reduce the code above like this:\n\n```scala\nSTextView(\"Hello\") textSize 15\n```\n\n**Further readings:**\n\n * [Return value of setters](https://github.com/pocorall/scaloid/wiki/Basics#wiki-return-value-of-setters)\n * [Prefixed classes](https://github.com/pocorall/scaloid/wiki/Basics#wiki-prefixed-classes)\n * [Sweet-little sugar](https://github.com/pocorall/scaloid/wiki/Basics#wiki-sweet-little-sugar)\n\n\n## Classes\n\n### Class `AlertDialogBuilder`\n\nA Scala-style builder for AlertDialog.\n \n```scala\nnew AlertDialogBuilder(R.string.title, R.string.message) {\n  neutralButton()\n}.show()\n```\n\nThis displays an alert dialog with given string resources. We provide an equivalent shortcut:\n\n```scala\nalert(R.string.title, R.string.message)\n```\n\nAlso you can build a more complex dialog:\n\n```scala\nnew AlertDialogBuilder(\"Exit the app\", \"Do you really want to exit?\") {\n  positiveButton(\"Exit\", finishTheApplication())\n  negativeButton(android.R.string.cancel)\n}.show()\n```\n\nThe code above is equivalent to:\n\n```scala\nnew AlertDialog.Builder(context)\n  .setTitle(\"Exit the app\")\n  .setMessage(\"Do you really want to exit?\")\n  .setPositiveButton(\"Exit\", new DialogInterface.OnClickListener {\n    def onClick(dialog: DialogInterface, which: Int) {\n      finishTheApplication()\n    }\n  })\n  .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener {\n    def onClick(dialog: DialogInterface, which: Int) {\n      dialog.cancel()\n    }\n  }).show()\n```\n\nWhen you call `show()` or `alert` from non-UI thread, you [don't have to mind about threading](https://github.com/pocorall/scaloid/wiki/Basics#wiki-asynchronous-task-processing).\n\n### Class `SArrayAdapter`\n\nSuppose you want to let the user selects a string from spinner, and larger font should be displayed in the dropdown list.\nThen the plain-old Android code is consisted of a chunk of XML and its wiring:\n\n```XML\n\u003c?xml version=\"1.0\" encoding=\"utf-8\"?\u003e\n\u003cTextView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    style=\"?android:attr/spinnerDropDownItemStyle\"\n    android:id=\"@+id/spinner_textview\"\n    android:layout_width=\"fill_parent\"\n    android:layout_height=\"wrap_content\"\n\tandroid:textSize=\"25.dip\" /\u003e\n```\n```scala\nval adapter = new ArrayAdapter(context, android.R.layout.simple_spinner_item, Array(\"One\", \"Two\", \"Three\"))\nadapter.setDropDownViewResource(R.layout.spinner_dropdown)\n```\n\nIn Scaloid, a directly equivalent code is:\n\n```scala\nSArrayAdapter(\"One\", \"Two\", \"Three\").dropDownStyle(_.textSize(25.dip))\n```\n\nIf you want to let the text color in the spinner be blue, use the `style` method:\n\n```scala\nSArrayAdapter(\"Quick\", \"Brown\", \"Fox\").style(_.textColor(Color.BLUE))\n```\n\nCan it be simpler?\n\n### Class `LocalService`\n\n[Android Developer Guide on service binding](http://developer.android.com/guide/components/bound-services.html) says that we have to write more than 60 lines of code to define and bind an in-process service. \nWith Scaloid, you can concisely define and access local service as shown below:\n\n```scala\nclass MyService extends LocalService {\n  private val generator = new Random()\n\n  def getRandomNumber() = generator.nextInt(100)\n}\n\nclass Activity extends SActivity {\n  val random = new LocalServiceConnection[MyService]\n \n  def onButtonClick(v:View) {\n    random( s =\u003e toast(\"number: \" + s.getRandomNumber()))\n  }\n}\n```\n\n**Further reading:** Refer to [this blog post](http://blog.scaloid.org/2013/03/introducing-localservice.html) to see why this is awesome in compared with the existing method.\n\n### Class `Preferences`\n\nSharedPreference can be accessed in this way:\n\n```scala\nval executionCount = preferenceVar(0) // default value 0\nval ec = executionCount() // read\nexecutionCount() = ec + 1 // write\nexecutionCount.remove() // remove\n```\n\n**Further reading:**\n - [Type-safe SharedPreference](http://blog.scaloid.org/2015/07/type-safe-sharedpreference.html)\n - [A simple example: Prompt user to rate your app](http://blog.scaloid.org/2013/03/prompt-user-to-rate-your-android-app.html)\n\n## Extending View class\nOften we need to define a custom view widget for a specific requirement.\nTo do this, we define a class that inherits `android.widget.View` class or its subclass (e.g. `TextView` and `Button`).\nTo enable Scaloid extensions for this custom widget, you can define a class as follows:\n\n```scala\nclass MyView(implicit ctx: Context) extends View(ctx) with TraitView[MyView] {\n  def basis = this\n  \n  // custom code for MyView here\n}\n```\n\n## Let's make it together!\n\nScaloid is an Apache licensed project. \nIf you have any idea to improve Scaloid, feel free to open issues or post patches.\nIf you want look into inside of Scaloid, this document would be helpful:\n\n * [Inside Scaloid](https://github.com/pocorall/scaloid/wiki/Inside-Scaloid)\n\n \n### [List of projects using Scaloid](https://github.com/pocorall/scaloid/wiki/Appendix#wiki-list-of-projects-using-scaloid)\n\n### We are hiring!\nThe company behind Scaloid, onsquare is hiring Scala developers.\nWe are building [a music app](https://play.google.com/store/apps/details?id=com.soundcorset.client.android) and other amazing products.\nWe extensively uses Scaloid in our product, and probably it is the best reference of Scaloid application.\nFor more information about our company, please refer to our website http://o-n2.com .\nPlease send us your CV via email if you are interested in working at onsqure.\nWe are located at Incheon, Korea.\npocorall@gmail.com\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpocorall%2Fscaloid","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpocorall%2Fscaloid","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpocorall%2Fscaloid/lists"}