{"id":15040915,"url":"https://github.com/techyourchance/thread-poster","last_synced_at":"2025-08-02T11:09:54.735Z","repository":{"id":56212719,"uuid":"104691408","full_name":"techyourchance/thread-poster","owner":"techyourchance","description":"Lightweight library for explicit and unit testable multithreading in Android.","archived":false,"fork":false,"pushed_at":"2021-04-07T18:24:46.000Z","size":204,"stargazers_count":148,"open_issues_count":3,"forks_count":15,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-08-02T08:42:29.689Z","etag":null,"topics":["android","android-library","multithreading"],"latest_commit_sha":null,"homepage":"","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/techyourchance.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}},"created_at":"2017-09-25T01:50:18.000Z","updated_at":"2025-04-23T14:54:48.000Z","dependencies_parsed_at":"2022-08-15T14:50:17.591Z","dependency_job_id":null,"html_url":"https://github.com/techyourchance/thread-poster","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/techyourchance/thread-poster","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/techyourchance%2Fthread-poster","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/techyourchance%2Fthread-poster/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/techyourchance%2Fthread-poster/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/techyourchance%2Fthread-poster/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/techyourchance","download_url":"https://codeload.github.com/techyourchance/thread-poster/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/techyourchance%2Fthread-poster/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":268378811,"owners_count":24240896,"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","status":"online","status_checked_at":"2025-08-02T02:00:12.353Z","response_time":74,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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","android-library","multithreading"],"created_at":"2024-09-24T20:45:16.855Z","updated_at":"2025-08-02T11:09:54.710Z","avatar_url":"https://github.com/techyourchance.png","language":"Java","funding_links":[],"categories":["测试"],"sub_categories":[],"readme":"# ThreadPoster\n\nLightweight library for unit testable and expressive multi-threading in Android.\n\n**ThreadPoster is in public beta**. I'm actively looking for feedback on project's structure, naming and usability. Bug reports are also welcome, of course.\n\n## Install\n\nTo use ThreadPoster in your project, make sure that you have Maven Central set up in your root `build.gradle` script:\n\n```\nallprojects {\n    repositories {\n        google()\n        mavenCentral()\n    }\n}\n```\n\nThen add ThreadPoster as a dependency into your main module's (called `app` by default) `build.gradle` script:\n\n```\ndependencies {\n    implementation 'com.techyourchance:threadposter:1.0.1'\n}\n```\n\n## Usage\nAt the core of this library are two simple classes: `UiThreadPoster` and `BackgroundThreadPoster`.\n\n### Executing code on UI thread\nIn the following example, `updateText(String)` can be safely called on any thread. The actual UI update will always take place on UI thread:\n\n```java\npublic class TextUpdater {\n\n    private final UiThreadPoster mUiThreadPoster;\n    private final TextView mTxtSample;\n    \n    public void updateText(final String text) {\n        mUiThreadPoster.post(() -\u003e {\n            mTxtSample.setText(text);\n        });\n    }\n}\n```\n\n### Executing code on \"background\" thread\nIn the following example, `fetchAndCacheUserDetails(String)` can be safely called on any thread. The actual network request and data caching will always take place on background thread (non-UI thread):\n\n```java\npublic class UpdateUserDetailsUseCase {\n\n    private final BackgroundThreadPoster mBackgroundThreadPoster;\n    private final UserDetailsEndpoint mUserDetailsEndpoint;\n    private final UserDetailsCache mUserDetailsCache;\n\n    public void fetchAndCacheUserDetails(final String userId) {\n        mBackgroundThreadPoster.post(() -\u003e {\n            UserDetails userDetails = mUserDetailsEndpoint.fetchUserDetails(userId);\n            mUserDetailsCache.cacheUserDetails(userDetails);\n        });\n    }\n}\n```\n\n### Executing code on \"background\" thread and notifying Observers on UI thread\nIn the [following example](sample/src/main/java/com/techyourchance/threadposters/FetchDataUseCase.java), `fetchData()` can be safely called on any thread. The actual data fetch will always take place on background thread, and the observers will always be notified on UI thread:\n\n```java\npublic class FetchDataUseCase {\n\n    public interface Listener {\n        void onDataFetched(String data);\n        void onDataFetchFailed();\n    }\n\n    private final FakeDataFetcher mFakeDataFetcher;\n    private final BackgroundThreadPoster mBackgroundThreadPoster;\n    private final UiThreadPoster mUiThreadPoster;\n\n    public void fetchData() {\n        // offload work to background thread\n        mBackgroundThreadPoster.post(() -\u003e {\n            fetchDataSync();\n        });\n    }\n\n    @WorkerThread\n    private void fetchDataSync() {\n        try {\n            final String data = mFakeDataFetcher.getData();\n            mUiThreadPoster.post(() -\u003e {\n                notifySuccess(data); // notify listeners on UI thread\n            });\n        } catch (FakeDataFetcher.DataFetchException e) {\n            mUiThreadPoster.post(() -\u003e {\n                notifyFailure(); // notify listeners on UI thread\n            });\n        }\n\n    }\n\n    @UiThread\n    private void notifyFailure() {\n        for (Listener listener : mListeners) {\n            listener.onDataFetchFailed();\n        }\n    }\n\n    @UiThread\n    private void notifySuccess(String data) {\n        for (Listener listener : mListeners) {\n            listener.onDataFetched(data);\n        }\n    }\n\n}\n```\n\n## Unit Testing\n\nThis library allows for easy unit testing of multithreaded code.\n\n**Important note: no amount of unit testing can guarantee that your code is thread-safe. In other words, even if your unit tests pass, your code can still be subject to race conditions, deadlocks, livelocks, etc.**\n\nTo support unit testing, ThreadPosters library is shipped with test double implementations for both `UiThreadPoster` and `BackgroundThreadPoster`. The core feature of these test doubles is that they are truly multi-threaded. In other words, when you unit test using these test doubles, you exercise your code in real multi-threaded environment which is the best approximation of the real production setting.\n\n### Benefits and drawbacks of multi-threaded unit testing\n\nThe approach employed by ThreadPoster's test doubles has its benefits and drawbacks.\n\n**Benefits of multi-threaded unit testing:**\n1. Exercises the code in real production setting.\n2. Has a chance to find multi-threading bugs. This will manifest itself in the form of \"flaky\" tests (tests that fail ocassionally).\n\n**Drawbacks of multi-threaded unit testing:**\n1. Longer unit tests execution times.\n2. Requires user assistance in the form of an additional step in each test case (`join()` calls in the examples below).\n\nOn my machine, test cases that use ThreadPoster test doubles execute in ~10ms (as opposed to \u003c1ms for plain Java). That's not an issue if you have 100 multi-threaded test cases, but it's a show stopper for proper TDD if you have 1000.\n\nThe upside is that absolute majority of your classes shoudln't be multi-threaded, which means that the overall percentage of slow unit tests should be low.\n\nI'm currently working on ways to optimize the test times, but you should definitely keep this drawback in mind if you intend to unit test using ThreadPoster test doubles.\n\n### Example unit test\n\nBelow code shows a unit test that makes use of ThreadPoster test doubles. It's part of the sample project.\n\nNote the calls to `mThreadPostersTestDouble.join()` in tests - that's the drawback number two. Since test cases become multithreaded, JUnit can't control tests' execution by itself anymore. \nTherefore, you'll need to call `mThreadPostersTestDouble.join()` before the assertions stage in each of your test cases. This makes sure that all involved threads run to completion and their side effects will be visible during assertions stage.\n\n```java\npublic class FetchDataUseCaseTest {\n\n    private static final String TEST_DATA = \"testData\";\n\n    private ThreadPostersTestDouble mThreadPostersTestDouble = new ThreadPostersTestDouble();\n\n    private FakeDataFetcher mFakeDataFetcherMock;\n    private FetchDataUseCase.Listener mListener1;\n    private FetchDataUseCase.Listener mListener2;\n\n    private FetchDataUseCase SUT;\n\n    @Before\n    public void setup() throws Exception {\n        mFakeDataFetcherMock = mock(FakeDataFetcher.class);\n\n        SUT = new FetchDataUseCase(\n                mFakeDataFetcherMock,\n                mThreadPostersTestDouble.getBackgroundTestDouble(),\n                mThreadPostersTestDouble.getUiTestDouble());\n\n        mListener1 = mock(FetchDataUseCase.Listener.class);\n        mListener2 = mock(FetchDataUseCase.Listener.class);\n    }\n\n    @Test\n    public void fetchData_successNoListeners_completesWithoutErrors() throws Exception {\n        // Arrange\n        when(mFakeDataFetcherMock.getData()).thenReturn(TEST_DATA);\n        // Act\n        SUT.fetchData();\n        // Assert\n\n        // needs to be called before assertions in order for all threads to complete and\n        // all side effects to be present\n        mThreadPostersTestDouble.join();\n\n        assertThat(true, is(true));\n    }\n\n    @Test\n    public void fetchData_successMultipleListeners_notifiedWithCorrectData() throws Exception {\n        // Arrange\n        when(mFakeDataFetcherMock.getData()).thenReturn(TEST_DATA);\n        ArgumentCaptor\u003cString\u003e ac = ArgumentCaptor.forClass(String.class);\n        // Act\n        SUT.registerListener(mListener1);\n        SUT.registerListener(mListener2);\n        SUT.fetchData();\n        // Assert\n\n        // needs to be called before assertions in order for all threads to complete and\n        // all side effects to be present\n        mThreadPostersTestDouble.join();\n\n        verify(mListener1).onDataFetched(TEST_DATA);\n        verify(mListener2).onDataFetched(TEST_DATA);\n    }\n\n    @Test\n    public void fetchData_failureMultipleListeners_notifiedOfFailure() throws Exception {\n        // Arrange\n        doThrow(new FakeDataFetcher.DataFetchException()).when(mFakeDataFetcherMock).getData();\n        // Act\n        SUT.registerListener(mListener1);\n        SUT.registerListener(mListener2);\n        SUT.fetchData();\n        // Assert\n\n        // needs to be called before assertions in order for all threads to complete and\n        // all side effects to be present\n        mThreadPostersTestDouble.join();\n\n        verify(mListener1).onDataFetchFailed();\n        verify(mListener2).onDataFetchFailed();\n    }\n\n}\n```\n\n## License\n\nThis project is licensed under the Apache-2.0 License - see the [LICENSE.txt](LICENSE.txt) file for details\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftechyourchance%2Fthread-poster","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftechyourchance%2Fthread-poster","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftechyourchance%2Fthread-poster/lists"}