soundcloud / lightcycle Goto Github PK
View Code? Open in Web Editor NEWLightCycle lets self-contained classes respond to Android’s lifecycle events
Home Page: http://soundcloud.github.io/lightcycle/
License: Apache License 2.0
LightCycle lets self-contained classes respond to Android’s lifecycle events
Home Page: http://soundcloud.github.io/lightcycle/
License: Apache License 2.0
Cf : #61
Fail fast and send a NullPointerException
when the binding a null
LightCycle to any dispatcher.
Currently, there's no way to handle activity result in activity controller.
Add an option to handle onBackPressed in activity controller. Potentially, something like boolean handleBackPress()
.
https://opus.soundcloud.com/display/ENGFUNC/Open+source+guidelines
Specifically :
example
I'm having some troubles using a custom dispatcher. When I dispatch the onCreate
I get a NullPointerException
:
Here is my structure:
public class HomeActivity extends AppCompatActivity implements LightCycleDispatcher<ActivityLightCycle<HomeActivity>> {
private final ActivityLightCycleDispatcher<HomeActivity> lightCycleDispatcher;
private ActivityHomeBinding binding;
@LightCycle FeedViewAction feedViewAction;
public HomeActivity() {
lightCycleDispatcher = new ActivityLightCycleDispatcher<>();
}
@Override
public void bind(ActivityLightCycle<HomeActivity> lightCycle) {
lightCycleDispatcher.bind(lightCycle);
}
public static Intent getCallingIntent(@NonNull final Context context) {
return new Intent(context, HomeActivity.class);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this, R.layout.activity_home);
setupToolbar();
LightCycles.bind(this);
lightCycleDispatcher.onCreate(this, savedInstanceState);
}
...
}
public class FeedViewAction extends BaseViewAction<HomeActivity> {
@Override
public void onCreate(HomeActivity activity, Bundle bundle) {
super.onCreate(activity, bundle);
activity.setupFeedViews();
}
}
public abstract class BaseViewAction<A extends Activity> extends DefaultActivityLightCycle<A> {
protected final CompositeSubscription compositeSubscription = new CompositeSubscription();
@Override
public void onDestroy(A activity) {
compositeSubscription.clear();
super.onDestroy(activity);
}
}
I've reviewed what could be wrong, and either is a silly mistake I made, or the documentation is not completed at all. Actually I was going to fork the repo and update it, because I think there should be a sample or at least a more completed HowTo
use a custom dispatcher.
Thanks in advance!
AppCompatDelegate
was recently introduced [1] in support to encourage composition over inheritance. It may play well with lightcycle. It would require to forward more life cycle callback though (onPostCreate
...).
The implementation would be something like that [3].
@iluu what do you think ?
[1] http://android-developers.blogspot.co.uk/2015/04/android-support-library-221.html
[2] http://developer.android.com/reference/android/support/v7/app/AppCompatDelegate.html?utm_campaign=ASL221-415&utm_source=dac&utm_medium=blog
[3] https://android.googlesource.com/platform/development/+/master/samples/Support7Demos/src/com/example/android/supportv7/app/AppCompatPreferenceActivity.java
In the fragment LightCycles, this would be pretty handy. The only thing I am not sure of is where we should call LightCycleBinder.bind(this), because onAttach can happen before onCreate, but if you are retaining a fragment, you will get multiple calls to onAttach per Fragment.
Thoughts @Lungos @mttkay ? Happy to contribute the PR myself.
There's a way to handle menu item clicks, but no way to inflate or modify menu items in the activity controller.
I believe that examples are not correct, because LightCycleSupportFragment only accepts fragment and not interfaces.
I'm having trouble integrating this with my application, because extending my fragment LightCycleSupportFragment I keep having:
java.lang.RuntimeException: Unable to start activity java.lang.NullPointerException: Attempt to invoke interface method 'void com.soundcloud.lightcycle.SupportFragmentLightCycle.onAttach(android.support.v4.app.Fragment, android.app.Activity)' on a null object reference
I'm actually trying to use this with Dagger2 but until now without success:
public class MyFragment extends LightCycleSupportFragment<MyFragment> implements IMyFragmentView{
@Inject
@LightCycle
public DialogManager mDialogManager;
public static Fragment newInstance() {
return new MyFragment();
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
injectFragment(getContext());
View view = inflater.inflate(R.layout.mylayout, container, false);
ButterKnife.bind(this, view);
setUIComponents();
return view;
}
private void injectFragment(Context context) {
MyFragmentComponent myComponent = DaggerMyFragmentComponent.builder()
.fragmentModule(new FragmentModule())
.build();
myComponent.inject(context);
}
}
DialogManager Implementation:
public class DialogManager extends DefaultSupportFragmentLightCycle{
@Inject
public DialogManager() {
}
}
-[x] add tests for annotation processor
-[ ] move library tests from main app to this project, rewrite using Robolectric 2
Hello,
I'm trying to reproduce the same behavior as LightCycleAppCompatActivity in a BaseActivity, so I implemented LightCycleDispatcher<ActivityLightCycle<T>>
in my base class and dispatch event in each lifecycle callback.
My base class prototype :
public abstract class BaseActivity<ActivityType extends BaseActivity> extends AppCompatActivity
implements LightCycleDispatcher<ActivityLightCycle<ActivityType>>
which is the same as LightCycleAppCompatActivity :
public abstract class LightCycleAppCompatActivity<ActivityType extends LightCycleAppCompatActivity>
extends AppCompatActivity
implements LightCycleDispatcher<ActivityLightCycle<ActivityType>>
The issue is when I use my base class LightCycleBinder cannot resolve the type of my child activity and generate this code below :
public final class MainActivity$LightCycleBinder {
public static void bind(MainActivity target) {
final com.soundcloud.lightcycle.ActivityLightCycle<ActivityType> mPresenter$Lifted = com.soundcloud.lightcycle.LightCycles.lift(target.mPresenter);
target.bind(mPresenter$Lifted);
}
}
But when my MainActivity extends directly LightCycleAppCompatActivity the type is revolved :
public final class MainActivity$LightCycleBinder {
public static void bind(MainActivity target) {
final com.soundcloud.lightcycle.ActivityLightCycle<MainActivity> mPresenter$Lifted = com.soundcloud.lightcycle.LightCycles.lift(target.mPresenter);
target.bind(mPresenter$Lifted);
}
}
I tried to create another base class with exactly the same code contain in LightCycleAppCompatActivity but it still doesn't work, it's seems to work only with LightCycleAppCompatActivity.
It's there any way to make it work using my BaseActivity class
Since the processor is a tiny module (1 class with 200- lines [1]) does it worth having it in a seperate module ?
Pros / Cons for merging the 2 modules
Pro
Cons
Note : I just noticed that Butterknife [2] embed the processor [3] for instance.
Question ?
[1] https://github.com/soundcloud/lightcycle/blob/master/lightcycle-processor/src/main/java/com/soundcloud/android/lightcycle/LightCycleProcessor.java
[2] https://github.com/JakeWharton/butterknife
[3] https://github.com/JakeWharton/butterknife/search?utf8=%E2%9C%93&q=ButterKnifeProcessor
▲ ▲ ▲ /!\ This is a draft▲ ▲ ▲
🚧
Lately, @jenzz created this 3 PRs to dispatch more callbacks :
This exposes the fact that currently the LC API is not consistent. For example ActivityLightCycle provides onCreate
, onOptionsItemSelected
but not onPostCreate
.
Initially, LightCycle were meant to remove the boilerplate to hook up a presenter to an Activity/Fragment lifecycle. The library was basically handling the same callbacks than ActivityLifecycleCallbacks. But, as we used it more and more across the SoundCloud app and other companies apps, we ended adding others callbacks as we felt the need.
We even added boolean onOptionsItemSelected(T activity, MenuItem item);
. On the other hand, we decided to not add onCreateOptionsMenu
since it was decided it would be the responsibility of the activity to inflate layouts.
Let's rethink the API
// TODO
// TODO
When compiling in AS JavaWriter is not found. Using the cli it works.
I have AS 1.5.1
If you create an activity which extends LightCycleAppCompatActivity
and it contains a fragment which extends LightCycleSupportFragment
, the activity receives a callback to onOptionsItemSelected
when you select an option item (as usual), but the fragment doesn't.
It seems like this might be due to LightCycleAppCompatActivity
not calling super.onOptionsItemSelected()
- therefore the event doesn't get propagated to the fragments in the activity.
Hello,
I followed this example : https://github.com/soundcloud/lightcycle/blob/master/lightcycle-lib/src/main/java/com/soundcloud/lightcycle/LightCycleSupportFragment.java
to build my parent class wich will be inherited by all fragments :
public abstract class M360LightCycleSupportFragment<FragmentType extends M360Fragment> extends M360Fragment implements LightCycleDispatcher<SupportFragmentLightCycle<FragmentType>> {
private final SupportFragmentLightCycleDispatcher<FragmentType> lifeCycleDispatcher;
private boolean bound;
public M360LightCycleSupportFragment() {
lifeCycleDispatcher = new SupportFragmentLightCycleDispatcher<>();
}
@Override
public void bind(SupportFragmentLightCycle<FragmentType> lifeCycleComponent) {
Preconditions.checkBindingTarget(lifeCycleComponent);
lifeCycleDispatcher.bind(lifeCycleComponent);
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
bindIfNecessary();
lifeCycleDispatcher.onAttach(fragment(), activity);
}
private void bindIfNecessary() {
if (!bound) {
LightCycles.bind(this);
bound = true;
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
lifeCycleDispatcher.onCreate(fragment(), savedInstanceState);
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
lifeCycleDispatcher.onViewCreated(fragment(), view, savedInstanceState);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
lifeCycleDispatcher.onActivityCreated(fragment(), savedInstanceState);
}
@Override
public void onStart() {
super.onStart();
lifeCycleDispatcher.onStart(fragment());
}
@Override
public void onResume() {
super.onResume();
lifeCycleDispatcher.onResume(fragment());
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
return lifeCycleDispatcher.onOptionsItemSelected(fragment(), item);
}
@Override
public void onPause() {
lifeCycleDispatcher.onPause(fragment());
super.onPause();
}
@Override
public void onStop() {
lifeCycleDispatcher.onStop(fragment());
super.onStop();
}
@Override
public void onSaveInstanceState(Bundle outState) {
lifeCycleDispatcher.onSaveInstanceState(fragment(), outState);
super.onSaveInstanceState(outState);
}
@Override
public void onDestroyView() {
lifeCycleDispatcher.onDestroyView(fragment());
super.onDestroyView();
}
@Override
public void onDestroy() {
lifeCycleDispatcher.onDestroy(fragment());
super.onDestroy();
}
@Override
public void onDetach() {
lifeCycleDispatcher.onDetach(fragment());
super.onDetach();
}
@SuppressWarnings("unchecked")
private FragmentType fragment() {
return (FragmentType) this;
}
}
Then my Fragment :
public class ProgramDetailsFragment extends M360LightCycleSupportFragment<ProgramDetailsFragment> {
@LightCycle
final ProgramPresenter programPresenter = new ProgramPresenter();
[...]
my Controller :
public class ProgramPresenter extends DefaultSupportFragmentLightCycle<ProgramDetailsFragment> {
private static final String TAG = "ProgramPresenter";
@Override
public void onAttach(ProgramDetailsFragment fragment, Activity activity) {
Log.i(TAG, "onAttach");
super.onAttach(fragment, activity);
}
[...]
when I extends this class in my code I obtain the following error at compilation :
Error:(5, 63) error: cannot find symbol class FragmentType
Is there something I didn't understand ? It feels like it won't be possible
A gist of a working 'parent fragment dispatcher' - 'inherited fragment' - 'controller' would be the perfect resource for me, maybe it already exists.
Thank you all,
Fail the build when the code style is not applied.
LightCycles.java
currently logs its search for the LightCycle binder class.
While this might be valuable to debug some issues I feel like we should provide an option to turn this logging off completely to not add extra noise to logs of applications using LightCycle.
Hello,
Thanks for this project, It's way better that what I tried to implement myself.
There is a thing that I miss thought, I have an activity with many fragments and many fragmentsPresenters.
Sometimes I do computation on my activity presenter and I want to send it to one or many of the fragment presenters (for example my table of content is displayes in the activity menu, and in a fragment that is shown full screen at the beginning).
How to I add a keep a reference of fragment presenters in my activity presenters (maybe it's not how I'm supposed to design it).
Second question. I have MyActivityPresenter
that has two children : MyOnlineActivityPresenter
and MyOfflineActivityPresenter
.
MyActivityPresenter.newPresenter(Network.isNetworkAvailable(contexte), few other args) decides whearas an online or offline presenter is instanciated. So I should do something like :
@LightCycle
PlayerPresenter presenter = PlayerPresenter.get(NetworkUtils.isNetworkAvailable(this));
But I've been told that I should never use context that way as It could be null at the class instanciation moment. Is it indeed a problem ?
also should I pass the few others arguments that I have in the onCreate
Bundle
?
And I don't use dependency injection at the moment.
I hope that I'm clear,
thanks again for this very useful lib
Maybe forward postCreate
instead ?
(mk) To fill in more context: this hook was introduced because in listeners we have a LightCycle which needs to access the screen layout in onCreate; however, onCreate is called before the subclass gets a chance to perform layout inflation, so this breaks. Another option would then be to have an optional onPostCreate
hook instead.
Do you guys feel the need for integration test?
Currently there are individual test per module (lightcycle-lib, lightcycle-processor, lightcycle-api) but there are no integration test.
This time I asked first because I want to make sure there are no special reason for not implementing integration test.
Thanks!
It is currently in com.soundcloud.android.lightcycle
because of its legacy from the listener app (same for propeller)
Maybe an exemple to solve the common problem "Checking if an Android application is running in the background / foreground" [1]. I.e, a @LightCycle
with @Singleton
and a isForeground
method.
Other ideas ?
Since we're now minSdkLevel 14, we should spend some time investigating if there's an opportunity to get rid of the forced inheritance from LightCycle base Activities. These only exist to forward life-cycle callbacks.
Android already has a mechanism for this, added in ICS:
http://developer.android.com/reference/android/app/Application.ActivityLifecycleCallbacks.html
http://developer.android.com/reference/android/app/Application.html#registerActivityLifecycleCallbacks(android.app.Application.ActivityLifecycleCallbacks)
However, no such thing exists for fragments to the best of my knowledge.
Can copy the setup from Memento.
Find out if SoundCloud has an account on Sonatype OSS Nexus
Hi guys!
First of all, awesome job! I really think having the change to split the bunch of logic in a Presenter, is the way to go.
I was wondering how do you manage to support custom views. I want to add a "presenter" to a custom view which does an specific logic, so I don't to depend on fragments or activities. If you are not handling view life cycles yet, I could try to have a deeper look and do a PR, but I wanted to ask first.
Thanks in advance again.
It is nice to have.
Goal :
LightCycleAppCompatActivity
forces you to use a theme that inherits from the AppCompat
theme. We should also provide a version that extends Activity
rather than AppCompatActivity
to support Android TV etc.
In basic example we have SampleActivity
https://github.com/soundcloud/lightcycle/blob/master/examples/basic/src/main/java/com/soundcloud/lightcycle/sample/basic/SampleActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
What is the purpose of this overriding, if you didn't add this method to SampleActivity at all effect would have been the same, wouldn't it?
To detect LC not forwarding to the dispatcher.
This could be done like we do w/ base activities and fragments.
On the example we would go from binding:
com.soundcloud.lightcycle.sample.real_world.HomeActivity
com.soundcloud.lightcycle.sample.real_world.HomePresenter
to binding:
com.soundcloud.lightcycle.sample.real_world.HomeActivity
com.soundcloud.lightcycle.ActivityLightCycleDispatcher
java.lang.Object
com.soundcloud.lightcycle.sample.real_world.HomePresenter
Custom BaseActivity
public abstract class RxLightCycleAppCompatActivity
<ActivityType extends RxLightCycleAppCompatActivity>
extends RxAppCompatActivity
implements LightCycleDispatcher<ActivityLightCycle<ActivityType>> {
private final ActivityLightCycleDispatcher<ActivityType> lightCycleDispatcher;
public RxLightCycleAppCompatActivity() {
lightCycleDispatcher = new ActivityLightCycleDispatcher<>();
}
@Override
public void bind(ActivityLightCycle<ActivityType> lightCycle) {
lightCycleDispatcher.bind(lightCycle);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setActivityContentView();
LightCycles.bind(this);
lightCycleDispatcher.onCreate(activity(), savedInstanceState);
}
protected abstract void setActivityContentView();
[...]
@SuppressWarnings("unchecked")
private ActivityType activity() {
return (ActivityType) this;
}
}
MainActivity
public class MainActivity extends RxLightCycleAppCompatActivity<MainActivity> {
[...]
@Inject @LightCycle Tester tester;
[...]
}
Tester
public class Tester extends ActivityLightCycleDispatcher<MainActivity> {
[...]
}
Error:
MainActivity$LightCycleBinder.java:5: error: cannot find symbol
final com.soundcloud.lightcycle.ActivityLightCycle<ActivityType> tester$Lifted = com.soundcloud.lightcycle.LightCycles.lift(target.tester);
symbol: class ActivityType
location: class MainActivity$LightCycleBinder
MainActivity$LightCycleBinder
public final class MainActivity$LightCycleBinder {
public static void bind(MainActivity target) {
final com.soundcloud.lightcycle.ActivityLightCycle<ActivityType> tester$Lifted = com.soundcloud.lightcycle.LightCycles.lift(target.tester);
target.bind(tester$Lifted);
}
}
Users reported that the doc is not explicit enough, also some ppl are creating issues to ask questions which are actually already covered by the doc. When this can't be avoided, I am guessing the doc could be better, i.e. more readable than a 300+ lines README.md file.
Explore the possibility of having a github.io (or similar) web page to present the doc.
Requirement : all the doc artifacts must still be part of this repo.
We started parameterizing the component type in the dispatcher and related classes, but it's only half-implemented. For instance, LightCycleSupportFragment
does not accept a type parameter, but extends from and delegates to generic types.
@Lungos I believe this was done initially so that LightCycles
can receive callbacks with non-framework sub-types of Activity/Fragment; I'm wondering though if we should then make sure that type contracts are put in place across the board?
I believe it started here : 72fd74c
java.lang.RuntimeException: Stub!
at android.os.Bundle.<init>(Bundle.java:5)
at com.soundcloud.lightcycle.ActivityLightCycleDispatcherTest.dispatchOnRestoreInstanceState(ActivityLightCycleDispatcherTest.java:96)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
at org.mockito.internal.runners.JUnit45AndHigherRunnerImpl.run(JUnit45AndHigherRunnerImpl.java:37)
at org.mockito.runners.MockitoJUnitRunner.run(MockitoJUnitRunner.java:62)
at org.junit.runners.Suite.runChild(Suite.java:127)
at org.junit.runners.Suite.runChild(Suite.java:26)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
at org.junit.runner.JUnitCore.run(JUnitCore.java:160)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:78)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:212)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:68)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140)
To get rid of boiler plate in parent / child scenarios (see TrackLikesPresenter)
Hi there!
I'm having troubles with Proguard and FragmentLightCycle
. I hadn't had that problem before with Activities, but lately I've been working with some fragments, and it looks like the dispatcher doesn't work
and I'm pretty sure the error is related to proguard, because a non obfuscated apk is working perfectly. I've never had a bug related to an annotation processor. How could I help to fix this? There is no trace. What kind of information should I provide?
LightCycleFragment and LightCycleSupportFragment don't dispatch onSaveInstanceState.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.