{"id":19260614,"url":"https://github.com/evant/retain-state","last_synced_at":"2025-04-21T16:32:08.386Z","repository":{"id":57736737,"uuid":"42854263","full_name":"evant/retain-state","owner":"evant","description":"A dead simple way to retain some state thought configuration changes on Android","archived":false,"fork":false,"pushed_at":"2020-06-14T22:51:22.000Z","size":160,"stargazers_count":12,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"main","last_synced_at":"2024-06-06T15:06:12.974Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"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/evant.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-09-21T08:41:25.000Z","updated_at":"2024-06-06T15:06:12.975Z","dependencies_parsed_at":"2022-08-24T02:51:10.963Z","dependency_job_id":null,"html_url":"https://github.com/evant/retain-state","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/evant%2Fretain-state","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/evant%2Fretain-state/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/evant%2Fretain-state/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/evant%2Fretain-state/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/evant","download_url":"https://codeload.github.com/evant/retain-state/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":223871516,"owners_count":17217571,"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":[],"created_at":"2024-11-09T19:22:04.246Z","updated_at":"2024-11-09T19:22:04.889Z","avatar_url":"https://github.com/evant.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# retain-state\n[![Maven Central](https://maven-badges.herokuapp.com/maven-central/me.tatarka.retainstate/retainstate/badge.svg?style=flat)](https://maven-badges.herokuapp.com/maven-central/me.tatarka.retainstate/retainstate)\n\nA dead simple way to retain some state thought configuration changes on Android\n\n## What? Why?\nLoading some data in a background thread and then showing it in your app is so common it should be trivial to do. Unfortunately Android does not make this easy. You have to deal with the fact that Activities can be destroyed out from under you at any time. This even happens even on a configuration change where you probably want to just continue whatever background work you are doing and show it in the newly-created Activity. Android does provided some components to allow to do this (Loaders, Fragments with `setRetainInstance(true)`, Services) but they are all overly complicated and have clunky and sometimes inflexible apis.\n\nLuckily, there is a pair of methods that make retaining some state between configuration changes simple and easy and it's been there since Api 1: [onRetainNonConfigurationInstance](http://developer.android.com/reference/android/app/Activity.html#onRetainNonConfigurationInstance%28%29) and [getLastNonConfigutationInstance](http://developer.android.com/reference/android/app/Activity.html#getLastNonConfigurationInstance%28%29). This pair of methods allow you to preserve state across orientation changes! This library proviveds a super-simple (and by simple I mean \u003c 200 loc) way to hook into this mechanism.\n\n## Download\n\n```groovy\ncompile 'me.tatarka.retainstate:retainstate:0.4'\n```\n\n## Usage\nThe first step is to hook up `RetainState` to your Activity. I'd advise to do this in your base Activity, but you can do this in whatever Activity makes sense for your application.\n\n```java\npublic class BaseActivity extends Activity implements RetainState.Provider {\n  private RetainState retainState;\n\n  @Override\n  protected void onCreate(Bundle savedInstanceState) {\n      retainState = new RetainState(getLastNonConfigurationInstance());\n      super.onCreate(savedInstanceState);\n  }\n\n  @Override\n  public Object onRetainNonConfigurationInstance() {\n      return retainState.onRetain();\n  }\n\n  @Override\n  public RetainState getRetainState() {\n      if (retainState == null) {\n          throw new IllegalStateException(\"RetainState has not yet been initialized\");\n      }\n      return retainState;\n  }\n}\n```\n\nNote: If you are using `FragmentActivity` or `AppCompatActivity` you have to use `getLastCustomNonConfigurationInstance()` and override `onRetainCustomNonConfigurationInstance()` instead.\n\nNow you just have to use `RetainState` to obtain the instance you want to retain.\n\n```java\npublic class MainActivity extends BaseActivity {\n  private MyRetainedModel model;\n  \n  @Override\n  protected void onCreate(Bundle savedInstanceState) {\n    super.onCreate(savedInstanceState);\n    \n    model = RetainState.from(this).retain(R.id.my_id, new RetainState.OnCreate\u003cMyRetainedModel\u003e() {\n      @Override\n      public MyRetainedModel onCreate() {\n        return new MyRetainedModel();\n      }\n    });\n\n    // Do stuff with model\n    model.setListener(...);\n  }\n\n  @Override\n  protected void onDestroy() {\n    super.onDestroy();\n    // Make sure you remove any references that can cause your Activity to leak!\n    model.setListener(null);\n  }\n}\n```\n\n`RetainState.from()` can be used in any place where the provider is available or a context wrapper around it, like in an Activity, Fragment or a custom View.\n\nNote: Your id's *must* be unique for the given Activity. You can achieve this by using view id's, you own generated id's, or by hand crafting them yourself.\n\n# Fragments\n\nOptionally, you can extend support to fragments by nesting `RetainState` instances. Included is a library to easily obtain a scoped RetainState from a fragment. Note that it uses a Loader under the hood, so you shouldn't create another loader in the same fragment with an id of -1. You must use support lib `24.0.0` for proper fragment support because it fixes some important fragment-related bugs.\n\n```groovy\ncompile 'me.tatarka.retainstate:fragment:0.4'\n```\n\nJust make a fragment a provider, similarly to an activity.\n\n```java\npublic class BaseFragment extends Fragment implements RetainState.Provider {\n    private RetainState retainState;\n\n    @Override\n    public void onActivityCreated(@Nullable Bundle savedInstanceState) {\n        retainState = RetainStateFragment.from(this);\n        super.onActivityCreated(savedInstanceState);\n    }\n\n    @Override\n    public RetainState getRetainState() {\n        if (retainState == null) {\n            throw new IllegalStateException(\"RetainState has not yet been initialized\");\n        }\n        return retainState;\n    }\n}\n```\n\n# Loader\n\nThis repo also includes a super-simple loader implementation built on top of retain-state. It lets you easily load something in the background and then get callbacks on the main thread that fire at the appropriate times.\n\n## Download\n\n```groovy\ncompile 'me.tatarka.retainstate:loader:0.4'\n// Contains an AsyncTaskLoader and CursorLoader to mirror the ones in the support lib.\ncompile 'me.tatarka.retainstate:loader-support:0.4'\n// Takes an rxjava observable.\ncompile 'me.tatarka.retainstate:loader-rx:0.4'\n```\n\n## Usage\n\nYou obtain an instance of `LoaderManager` using retain-state to retain it, then you initialize one or more loaders with callbacks. Finally you use the methods `start()` or `restart()` on the loader to load the data and `cancel()` to cancel it. The callbacks will automatically re-deliver the correct results on a configuration change. Note: you do have to do a little cleanup when your Activity is destroyed to detach the callbacks.\n\n```java\npublic class MainActivity extends BaseActivity {\n    private LoaderManager loaderManager;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        loaderManager = getRetainState().retain(R.id.my_loader_manager, LoaderManager.CREATE);\n\n        final MyLoader loader = loaderManager.init(0, MyLoader.CREATE, new Loader.CallbacksAdapter\u003cString\u003e() {\n            @Override\n            public void onLoaderStart() {\n              // Update your ui to show you are loading something\n            }\n\n            @Override\n            public void onLoaderResult(String result) {\n              // Update your ui with the result\n            }\n\n            @Override\n            public void onLoaderComplete() {\n              // Optionally do something when the loader has completed\n            }\n        });\n\n        button.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                loader.restart();\n            }\n        });\n    }\n\n    @Override\n    protected void onDestroy() {\n        super.onDestroy();\n        // Loader cleanup, this is important to detach the loader from the Activity\n        loaderManager.onDestroy(getRetainState());\n    }\n}\n```\n\nor in a fragment\n\n```java\npublic class MyFragment extends BaseFragment {\n    LoaderManager loaderManager;\n\n    @Override\n    public void onActivityCreated(@Nullable Bundle savedInstanceState) {\n        super.onActivityCreated(savedInstanceState);\n        loaderManager = getRetainState().retain(R.id.my_loader_manager, LoaderManager.CREATE);\n    \n        final ModelLoader loader = loaderManager().init(0, ModelLoader.CREATE, new Loader.CallbacksAdapter\u003cString\u003e() {\n            @Override\n            public void onLoaderStart() {\n              // Update your ui to show you are loading something\n            }\n\n            @Override\n            public void onLoaderResult(String result) {\n              // Update your ui with the result\n            }\n        });\n\n        button.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                loader.restart();\n            }\n        });\n    }\n    \n    @Override\n    public void onDestroy() {\n        super.onDestroy();\n        loaderManager.onDestroy(getRetainState());\n    }\n}\n```\n\nTo implement a loader, you subclass `Loader` and override `onStart()` and optionally `onCancel()` and `onDestroy()`.\n\n```java\npublic class MyLoader extends Loader\u003cString\u003e {\n    // Convenience for loaderManager.init()\n    public static final RetainState.OnCreate\u003cMyLoader\u003e CREATE = new RetainState.OnCreate\u003cMyLoader\u003e() {\n        @Override\n        public MyLoader onCreate() {\n            return new MyLoader();\n        }\n    };\n    \n    @Override\n    protected void onStart(Receiver receiver) {\n        // Note loader doesn't handle threading, you have to do that yourself.\n        api.doAsync(new ApiCallback() {\n          public void onResult(String result) {\n            // Make sure this happens on the main thread!\n            receiver.deliverResult(result);\n            receiver.complete();\n          }\n        });\n    }\n\n    // Overriding this method is optional, but if you can cancel your call when it's no longer needed, you should.\n    @Override\n    protected void onCancel() {\n        api.cancel();\n    }\n    \n    // Overriding this method is optional and allows you to clean up any resources.\n    @Override\n    protected void onDestroy() {\n        \n    }\n}\n```\n\n## License\n\n    Copyright 2015 Evan Tatarka\n    \n    Licensed under the Apache License, Version 2.0 (the \"License\");\n    you may not use this file except in compliance with the License.\n    You may obtain a copy of the License at\n    \n       http://www.apache.org/licenses/LICENSE-2.0\n    \n    Unless required by applicable law or agreed to in writing, software\n    distributed under the License is distributed on an \"AS IS\" BASIS,\n    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n    See the License for the specific language governing permissions and\n    limitations under the License.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fevant%2Fretain-state","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fevant%2Fretain-state","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fevant%2Fretain-state/lists"}