{"id":13658911,"url":"https://github.com/spotify/mobius","last_synced_at":"2025-05-14T08:06:31.507Z","repository":{"id":41407083,"uuid":"118132535","full_name":"spotify/mobius","owner":"spotify","description":"A functional reactive framework for managing state evolution and side-effects.","archived":false,"fork":false,"pushed_at":"2024-12-12T09:04:29.000Z","size":1624,"stargazers_count":1237,"open_issues_count":4,"forks_count":102,"subscribers_count":47,"default_branch":"master","last_synced_at":"2025-05-11T10:57:01.954Z","etag":null,"topics":["android","functional-reactive-programming","java","mobius","state-management"],"latest_commit_sha":null,"homepage":"https://spotify.github.io/mobius/","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/spotify.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","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":"2018-01-19T14:07:51.000Z","updated_at":"2025-05-07T17:15:27.000Z","dependencies_parsed_at":"2024-01-04T10:28:25.092Z","dependency_job_id":"55515b28-4906-4831-82b5-1d3e31716864","html_url":"https://github.com/spotify/mobius","commit_stats":{"total_commits":368,"total_committers":30,"mean_commits":"12.266666666666667","dds":0.5788043478260869,"last_synced_commit":"a629cd880939ccde58fc949660f91191cb4e5168"},"previous_names":[],"tags_count":38,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spotify%2Fmobius","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spotify%2Fmobius/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spotify%2Fmobius/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spotify%2Fmobius/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/spotify","download_url":"https://codeload.github.com/spotify/mobius/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254101616,"owners_count":22014909,"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":["android","functional-reactive-programming","java","mobius","state-management"],"created_at":"2024-08-02T05:01:03.683Z","updated_at":"2025-05-14T08:06:26.474Z","avatar_url":"https://github.com/spotify.png","language":"Java","readme":"![Mobius Logo](/docs/assets/images/mobius-logo.png)\n\n[![Maven Central](https://img.shields.io/maven-central/v/com.spotify.mobius/mobius-core.svg)](https://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.spotify.mobius%22)\n[![Code Coverage](https://codecov.io/gh/spotify/mobius/branch/master/graph/badge.svg)](https://codecov.io/gh/spotify/mobius)\n[![License](https://img.shields.io/github/license/spotify/mobius.svg)](LICENSE)\n[![Join the chat at https://gitter.im/spotify/mobius](https://badges.gitter.im/spotify/mobius.svg)](https://gitter.im/spotify/mobius)\n\nMobius is a functional reactive framework for managing state evolution and side-effects, with add-ons for connecting to Android UIs and RxJava Observables. It emphasizes separation of concerns, testability, and isolating stateful parts of the code.\n\nTo learn more, visit the [website][mkdocs] for a user guide. To see Mobius in action, check out the [sample TODO](https://github.com/spotify/mobius-android-sample) app based on the app from [Android Architecture Blueprints](https://github.com/googlesamples/android-architecture). You can also\nwatch a [talk from Android @Scale introducing Mobius](https://www.facebook.com/atscaleevents/videos/2025571921049235/).\n\n## Status\n\nMobius is in Production status, meaning it is used in production in Spotify Android applications, and that we consider the APIs to be stable and the implementation bug-free. We will not make backwards-compatibility-breaking changes.\n\nMobius is currently built for Java 7 (because Java 8 is not fully supported on all versions of Android), hence the duplication of some concepts defined in `java.util.function` (see `com.spotify.mobius.functions`).\n\nWhen using Mobius, we recommend using Kotlin or Java 8 or later, primarily because of the improved type inference and because using lambdas greatly improves readability and conciseness of code.\n\n## Using it in your project\n\nThe latest version of Mobius is available through Maven Central (LATEST_RELEASE below is ![latest not found](https://img.shields.io/maven-central/v/com.spotify.mobius/mobius-core.svg)):\n\n```groovy\nimplementation 'com.spotify.mobius:mobius-core:LATEST_RELEASE'\ntestImplementation 'com.spotify.mobius:mobius-test:LATEST_RELEASE'\n\nimplementation 'com.spotify.mobius:mobius-rx:LATEST_RELEASE'       // only for RxJava 1 support\nimplementation 'com.spotify.mobius:mobius-rx2:LATEST_RELEASE'      // only for RxJava 2 support\nimplementation 'com.spotify.mobius:mobius-rx3:LATEST_RELEASE'      // only for RxJava 3 support\nimplementation 'com.spotify.mobius:mobius-android:LATEST_RELEASE'  // only for Android support\nimplementation 'com.spotify.mobius:mobius-extras:LATEST_RELEASE'   // utilities for common patterns\n```\n\n### mobius-core [![Javadocs](http://www.javadoc.io/badge/com.spotify.mobius/mobius-core.svg?color=blue)](http://www.javadoc.io/doc/com.spotify.mobius/mobius-core)\nThis is the core of Mobius, which all other modules depend on. It is a pure Java library that is completely self-contained. This is the only module that you need when using Mobius, because the others are optional extensions to the core.\n\n### mobius-test [![Javadocs](http://www.javadoc.io/badge/com.spotify.mobius/mobius-test.svg?color=blue)](http://www.javadoc.io/doc/com.spotify.mobius/mobius-test)\nThe test module contains utilities that help you write tests for Mobius applications. It should only be used as a test dependency.\n\n### mobius-rx [![Javadocs](http://www.javadoc.io/badge/com.spotify.mobius/mobius-rx.svg?color=blue)](http://www.javadoc.io/doc/com.spotify.mobius/mobius-rx) / mobius-rx2  [![Javadocs](http://www.javadoc.io/badge/com.spotify.mobius/mobius-rx2.svg?color=blue)](http://www.javadoc.io/doc/com.spotify.mobius/mobius-rx2) / mobius-rx3 [![Javadocs](http://www.javadoc.io/badge/com.spotify.mobius/mobius-rx3.svg?color=blue)](http://www.javadoc.io/doc/com.spotify.mobius/mobius-rx3)\nThe rx modules contain extensions for RxJava. You should use one of them in your Mobius applications since they simplify creating effect handlers and event sources. Both RxJava modules share the same API, the only difference is that one is built for RxJava 1.x and the other for RxJava 2.x.\n\n### mobius-android [![Javadocs](http://www.javadoc.io/badge/com.spotify.mobius/mobius-android.svg?color=blue)](http://www.javadoc.io/doc/com.spotify.mobius/mobius-android)\nThe android module primarily contains classes for hooking up a MobiusLoop to Android.\n\n### mobius-extras [![Javadocs](http://www.javadoc.io/badge/com.spotify.mobius/mobius-extras.svg?color=blue)](http://www.javadoc.io/doc/com.spotify.mobius/mobius-extras)\nThe extras module contains utilities and classes that help reducing boilerplate for some more advanced usage patterns (for example, nested update functions).\n\n## Mobius in Action - Building a Counter\n\nThe goal of Mobius is to give you better control over your application state. You can think of your state as a snapshot of all the current values of the variables in your application. In Mobius, we encapsulate all of the state in a data-structure which we call the *Model*.\n\nThe *Model* can be represented by whatever type you like. In this example we'll be building a simple counter, so all of our state can be contained in an `Integer`:\n\nMobius does not let you manipulate the state directly. In order to change the state, you have to send the framework messages saying what you want to do. We call these messages *Events*. In our case, we'll want to increment and decrement our counter. Let's use an `enum` to define these cases:\n```java\nenum CounterEvent {\n  INCREMENT,\n  DECREMENT,\n}\n```\n\nNow that we have a *Model* and some *Event*s, we'll need to give Mobius a set of rules which it can use to update the state on our behalf. We do this by giving the framework a function which will be sequentially called with every incoming *Event* and the most recent *Model*, in order to generate the next *Model*:\n```java\nclass CounterLogic {\n  static Integer update(Integer model, CounterEvent event) {\n    switch (event) {\n      case INCREMENT: return model + 1;\n      case DECREMENT: return model - 1;\n    }\n  }\n}\n```\n\nWith these building blocks, we can start to think about our applications as transitions between discrete states in response to events. But we believe there is still one piece missing from the puzzle - namely the side-effects which are associated with moving between states. For instance, pressing a \"refresh\" button might put our application into a \"loading\" state, with the side-effect of also fetching the latest data from our backend.\n\nIn Mobius, we aptly call these side-effects *Effect*s. In the case of our counter, let's say that when the user tries to decrement below 0, we play a sound effect instead. Let's create an `enum` that represents all the possible effects (which in this case is only one):\n```java\nenum CounterEffect {\n  PLAY_SOUND,\n}\n```\n\nWe'll now need to augment our `update` function to also return a set of effects associated with certain state transitions. To do this we'll implement the `Update` interface like so:\n\n```java\nclass CounterLogic implements Update\u003cInteger, CounterEvent, CounterEffect\u003e {\n  public Next\u003cInteger, CounterEffect\u003e update(Integer model, CounterEvent event) {\n    switch (event) {\n      case INCREMENT:\n        return next(model + 1);\n      case DECREMENT:\n        if (model == 0) {\n          Set\u003cCounterEffect\u003e soundEffect = effects(CounterEffect.PLAY_SOUND);\n          return dispatch(soundEffect);\n        }\n        return next(model - 1);\n\n    }\n    throw new IllegalStateException(\"Unhandled event: \" + event);\n  }\n}\n```\n\nMobius sends each of the effects you return in any state transition to something called an *Effect Handler*. Let's make one of those now by implementing the `Connectable` interface:\n```java\nclass CounterEffectHandler implements Connectable\u003cCounterEffect, CounterEvent\u003e {\n  public Connection\u003cCounterEffect\u003e connect(Consumer\u003cCounterEvent\u003e output) {\n    return new Connection\u003cCounterEffect\u003e() {\n      @Override\n      public void accept(CounterEffect effect) {\n        if (effect == CounterEffect.PLAY_SOUND) {\n          Toolkit.getDefaultToolkit().beep();\n        }\n      }\n\n      @Override\n      public void dispose() {}\n    };\n  }\n}\n```\n\nNow that we have all the pieces in place, let's tie it all together:\n```java\npublic static void main(String[] args) {\n  // Let's make a Mobius Loop\n  MobiusLoop\u003cInteger, CounterEvent, CounterEffect\u003e loop = Mobius\n      .loop(new CounterLogic(), new CounterEffectHandler())\n      .startFrom(0);\n\n  // And start using our loop\n  loop.dispatchEvent(CounterEvent.INCREMENT); // Model is now 1\n  loop.dispatchEvent(CounterEvent.DECREMENT); // Model is now 0\n  loop.dispatchEvent(CounterEvent.DECREMENT); // Sound effect plays! Model is still 0\n}\n```\n\nThis covers the fundamentals of Mobius. To learn more, head on over to our [website][mkdocs].\n\n## Building\n\n### Formatting\n\nWe're using Google's auto-formatter to format the code. The build pipeline is set up to fail builds that aren't correctly formatted. To ensure correct formatting, run\n\n```bash\n./gradlew format\n```\n\n## Code of Conduct\n\nThis project adheres to the [Open Code of Conduct][code-of-conduct]. By participating, you are expected to honor this code.\n\n[mkdocs]: https://spotify.github.io/mobius/\n[code-of-conduct]: https://github.com/spotify/code-of-conduct/blob/master/code-of-conduct.md\n","funding_links":[],"categories":["Libraries","并发编程","Inspired by Elm","Java"],"sub_categories":["🏗 Architecture","Games"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fspotify%2Fmobius","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fspotify%2Fmobius","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fspotify%2Fmobius/lists"}