[OBSOLETE] Postmortem: How I got banned from /r/androiddev for being “too” active as a member of the community (and notes on Jetpack Compose, Dagger + ViewModel + SavedStateHandle, and transparency)

Gabor Varadi
14 min readMay 21, 2020

--

“Permanently banned from participation.” Join us at /r/android_devs.

Well, there you have it folks. After 5 years of active participation in the /r/androiddev community on Reddit, I’ve finally managed to “piss off” the primary active moderator (violation of Rule #10, daring to talk about it in a blog post), and now I’m permanently banned from participation.

Author’s note: This is not a call to action, with the new rules and framework of power in place, I don’t consider /r/androiddev a safe community to participate in anymore.

How? By answering questions, helping people with their problems, and the most criminal offense: pointing out if something has a particular drawback, omission, unintended side-effect — and therefore is “not correct”. Or at least doesn’t necessarily work as you’d expect.

Joke about Loaders vs ViewModel vs “But what about handling process death?”
Guide on how to get banned from /r/androiddev over the span of 5 years
Reddit activity in points

People seeking to educate themselves on how Android works, how the Android lifecycle works, what edge-cases to consider when developing Android applications, and those looking for credible, trustworthy resources on Android development had always been thankful for my contributions (even if some have since become obsolete) — as it can be quite difficult to navigate across the “minefield” of Android development.

What is truly a best practice? And what effects does it have by applying it in the code that drives our apps?

Outdated best practices

Remember Google I/O 2018, where Reto Meier (violation of Rule 10, “don’t talk about people’s work in a negative context”) presented a talk named Protips: a fresh look at advanced topics for Android experts? A talk dedicated to “outdated best practices”, and all the frustrations that could come from attempting to implement said practices. And all the things that have come to replace said “best practices” of the time.

Driven with the intention of helping people avoid potential mistakes or bugs:

You could at the time already use background jobs with android-priority-jobqueue (now OneTimeWorkRequest and WorkManager), combined with a reactive data source (at the time, Realm; now, Room).

But creating a reactive wrapper of your own over SQLiteOpenHelper was also an option.

………………………………………

  • And before the addition of viewmodel-savedstate and the SavedStateHandle, I pointed out (and so did Vasiliy Zukanov) that using ViewModel without consideration for onSaveInstanceState, you’re likely to either just sometimes forget user input after opening the Camera app, but worse-case you could either end up on an infinite loading screen (see #8), or more likely, your application could crash (see #6 and #7).

What are the next likely “best practices” to become obsolete?

LiveData -> StateFlow

First and foremost, with the way the development of Kotlin Flows is going, it’s likely that Jetpack LiveData will become obsolete in favor of StateFlow, in combination with lifecycleScope.launch {.

Even with the inclusion of the liveData { coroutine builder, there is less and less need for the operators (and potential custom operators like combining the emission of multiple LiveDatas) on top of MediatorLiveData, especially if StateFlow will already provide the same behavior, with more operators out of the box.

(Whether you still choose to stick with RxJava and BehaviorRelay instead is up to you, as Flows are still in active development, and oftentimes marked with @FlowPreview.)

Databinding -> Jetpack Compose

It is a no-brainer (violation of Rule #10, this word has negative implications for those who don’t know about the technology in question and therefore offensive, strictly not allowed, and potentially ban-worthy) that Jetpack Databinding will become obsolete once Jetpack Compose comes out: as you can directly bind a model class wrapped in a by state { obj } or by savedState {} delegate (or mutableStateOf()) — to “fuel” the rendering of your current layout. You don’t need to bind model properties to your XML, if you don’t have XML! 😏 (violation of Rule #10, smirking is disrespectful, unprofessional, and considered toxic)

Fragments -> Jetpack Navigation + Jetpack Compose

What is less obvious (violation of Rule #10, nothing is ever “obvious”, this word is not allowed) is that when you look at the latest developments of Jetpack Navigation, every destination (fragment or nav graph) has a corresponding NavBackStackEntry. These backstack entries implement ViewModelStoreOwner, SavedStateRegistryOwner, and LifecycleOwner. They also have their own arguments, passed in from the <action that creates them.

Sounds familiar? These interfaces are all implemented by ComponentActivity and Fragment.

Now imagine that you can create a destination, like ComposeDestination, that describes which @Composable method to show on the screen. With a ComposeNavigator that can register the active destination as a model for the composition context in setContent, this allows us to retrieve the NavBackStackEntry, potentially from an ambient, for our current active composable.

Why is this important? Because now we can obtain a ViewModel (see ViewModelStore), and we can even begin to observe lifecycles using Lifecycle (see LifecycleOwner), inside any composable, bound to the NavBackStackEntry.

Once you have a ViewModel/Lifecycle, and you exist in an enclosing Activity, do you need a “ViewController with lifecycle integration”? If your Composable methods can already retrieve a Lifecycle that belongs to them, when they are active? No!

Therefore, Jetpack Navigation 2.2.0+ along with Jetpack Compose is a direct replacement to Fragments.

Can you build this yourself, right now? Certainly. I intend to look into that in the future in more detail, when Compose starts being a bit more mature. Maybe even see what it looks like if you add NavDestinations and NavActions directly, rather than going through the NavInflater.

I’ve already played around a bit with Compose in dev-04 to see potential integration points, and the theory is sound (with simple-stack, as it offers very similar functionality, and therefore can be directly mapped to Jetpack Navigation in this regard.)

What are these Rule 10 violation markers you’re inlining in this post?

The premise of this post is that I was banned from /r/androiddev, right? The rule in question reads as follows:

Rule 10: Be respectful and engage in good faith

The Android developer community is a warm and friendly field and /r/androiddev strives to continue this. Engage in good-faith discussion and be respectful of others’ opinions, privacy, and intentions. Threads that violate this will be removed at mods’ discretion. This rule is intentionally broad, as toxic behavior comes in a variety of different forms. Examples: ad hominem, sealioning, targeted attacks on others’ work, edgelording, and other keyboard warrior behavior.

On the surface, there is nothing wrong with “wanting to build a friendly community where people engage in good faith and respect one another”.

A primer on Reddit global rules

This is already covered by the Reddiquette:

Please do:
- Remember the human.
- Adhere to the same standards of behavior online that you follow in real life.
- Moderate based on quality, not opinion.

Please don’t:
- Violate the User Agreement or the Content Policy.
- Post someone’s personal information
- Be (intentionally) rude at all.
- Insult others.
- Ask people to Troll others on reddit
- Follow those who are rabble rousing against another redditor without first investigating both sides of the issue that’s being presented.

Authors note: please keep the last one in mind. This is NOT a call for action. If you read the Conclusion, you’ll see there is absolutely no need for action. You shouldn’t mod-mail them or ask for clarification — it risks the safety of your account and access to /r/androiddev anyway.

To directly quote from the Reddiquette:

Insults do not contribute to a rational discussion. Constructive Criticism, however, is appropriate and encouraged.

I’d always like to believe that what I’ve provided in the past was always constructive criticism, and even if it did not seem that way, I was always ready to provide a detailed explanation of what I meant.

And really, we all know that “code can be bad”, even if it’s written by brilliant minds. Is saying “this is bad and you can improve it in these particular ways” actually toxic?

Is saying “this is wrong, you should be caching this LiveData value in your ViewModel instead of returning it directly” toxic?

According to Rule #10, definitely yes.

What else counts as a violation of Rule #10 on /r/androiddev?

You’ve already seen a few examples of “Rule #10 violation” in this post, I’ve marked them for your convenience. Allow me to show you the original two, ban-worthy violations (thankfully public communication, so I can share it with you) that have led me to be initially temporarily, then permanently banned.

Where the permanent ban came from posting about the temporary ban on Twitter.

Imagine what I’d get for posting this article, criticizing the new rule. Probably a permanent ban from /r/androiddev… wait, they’ve already used that silver bullet.

You can check the above, offending, “ban-worthy posts” to decide if I actually had “lashed out” against a mod, and if issuing a warning regarding proper state persistence is considered “slander”.

And yes, when I posted about this on Twitter, that qualified as “the third strike” and I was permanently banned for that.

Do I think this is reasonable moderation? If you ask me: no, obviously not — if I thought this is a sane moderation action, you wouldn’t be getting this awesome post 😉

Talking about Android celebrities is not allowed here, take your gossip elsewhere!

Of course, one could argue that “it’s not unclear if I can see which parts he finds questionable”.

Whether it’s justified is in the eye of the beholder. But whether it’s excessive, I think there are no arguments there.

In fact, reading the Reddit Moderator Guidelines for Healthy Communities (that’s a thing!), I find the following points surprisingly applicable:

Healthy communities are those where participants engage in good faith, and with an assumption of good faith for their co-collaborators. It’s not appropriate to attack your own users.

Management of your own Community
Clear, Concise, and Consistent Guidelines:

Healthy communities have agreed upon clear, concise, and consistent guidelines for participation. These guidelines are flexible enough to allow for some deviation and are updated when needed. Secret Guidelines aren’t fair to your users — transparency is important to the platform.

Did the community agree to this?

If you’ve been checking the downvote counts, do you think the “community” has “agreed upon clear, concise, and consistent guidelines” since the update?

What of the “slander” post that targets Vasiliy Zukanov’s work? Why is it still up?

Feel free to comment if I’m wrong, though (I do allow, in fact, encourage active discussions, as I’d like to know when I’m wrong — so that I can fix it, instead of silencing anyone who dares hold a different opinion).

But I do feel it’s hard to stay “in good faith” for “the moderation team” when they’ve clearly violated the trust of the community, repeatedly in the past few days, which is unfortunate. Maybe we can build a new one elsewhere?

A world without criticism in the name of “professionalism” and false positivity, as per Rule 10

Imagine that the only source of information you have is Google’s samples from the Android Jetpack Advocate Team (potential violation of Rule #10 rising).

If you take their code as a direct reference that you should be copying, then you’ll end up with the following two (questionable?) practices.

Jetpack ViewModel + Dagger (why you don’t need map multibinding)

You might think that in order to use Jetpack ViewModel with Dagger, then “you MUST use map-multibinding over Provider<? extends ViewModel>, bound to a @ViewModelKey that is the Class<? extends ViewModel> that is being injected”.

Thankfully, I can tell you that this assertion is false. Unlike WorkManager, where you are configuring 1 singleton global instance to have 1 single reference of a WorkerFactory that can instantiate N workers (same applies to FragmentFactory), in the case of ViewModel, each site that requires a ViewModel instance can get a reference using a ViewModelProvider(viewModelStore, viewModelProviderFactory) right there and then.

There is no need to configure a global factory, as you already know what ViewModel you need, a ViewModel always requires at least 1 ViewModelProvider.Factory, and to get the right instance of ViewModel, you can also provide the right ViewModelProvider.Factory.

Jetpack ViewModel + Dagger (what you can do instead)

This means that you can SAFELY create a ViewModelProvider.Factory per each ViewModel.

There’s a trick though which will make this approach shine: a ViewModelProvider.Factory can be created inline at the call-site, and delegate this over to an actual factory.

Taking this one step further, you can pass a lambda directly that would create a ViewModel… or delegate the creation of a ViewModel to something that can create it. (Dagger, anyone?)

Our first approach would look like this:

Jetpack ViewModel + Dagger (complications with SavedStateHandle and Jetpack Navigation)

However, if you were to use SavedStateHandle along with Jetpack Navigation with a NavGraph-scoped ViewModel, this would fail: you have to move the creation of ViewModel into onCreateView.

ViewModelProvider already guarantees that we only create the ViewModel if it is required (not yet in the ViewModelStore), regardless of which callback it’s in — we may as well create it on first access. How to do that in Kotlin? With delegates!

In fact, the above helper extensions are technically very similar to by viewModels in Android-KTX (except Android-KTX didn’t exist at the time when I wrote the reference I used from Stack Overflow. It was also cited by Fred Porciúncula’s amazing Dagger talk at Android Makers 2019).

So what can you do? Create the ViewModel only on first access (which, with a NavGraph-scoped ViewModel, when using <FragmentContainerView, only works inside onCreateView), and pass it a savedStateHandle (which requires a SavedStateRegistryOwner for it to be created).

But to pass in a SavedStateRegistryOwner into the graph, do I need subcomponents? Sure, you can do that, but I’ve tried it and then marked it deprecated in about a week. You can do better with Assisted-Injection, as you can avoid creating a subscoped component per each injection site.

What does it look like? I have a full working sample in this “Jetpack Navigation FTUE Sample”, and it looks like this:

Though if you don’t like the Injector.get() lookup, you can also inject it as a constructor argument, as long as you use a FragmentFactory (which I think can be trickier, but you can, nonetheless. For that scenario, it is indeed worth investigating map multi-binding over Fragment providers.)

One-off events vs. LiveData<Event<T>> (event wrappers)

I intended to write a section on how you can swap out LiveData<Event<T>> for something that doesn’t even have to be a LiveData (especially as we don’t want to retain any of LiveData’s inherent properties, except for emitting in onStart), and how you don’t need to follow Jose Alcerecca’s post on “how to handle one-off events” (Rule #10 violation, don’t make targeted attacks against other people’s work).

You can use EventEmitter instead (which I wrote for this purpose: to replace LiveData’s usage as a “one-off event handler” — which can handle multiple events, rather than retain only one), along with a LiveEvent wrapper (that lets you observe it in a lifecycle-aware way, just like if it were LiveData), so that you don’t need to worry about EventObserver, and you especially don’t need to create N liveData to communicate N one-off events.

Then I realized I’ve already described this approach in a previous article about WeakReferences, callbacks, and one-off events, so there’s no reason to go into it further 😉 (Rule violation #10: assuming people have read your articles and winking at them. Why? Not sure, but don’t ask the mods, that’s a violation, too.)

Nonetheless, here is a sample:

In fact, you can even utilize the power of EventEmitter, and send a lambda as an event: this lets you move Navigation commands to be implemented inside ViewModel, even without a direct reference to NavController.

Though I’ve also been informed that both LinkedListChannel, and eventually SharedFlow are/will be suitable replacements for modelling one-off events.

Conclusion

I’m not sure what to say, to be honest. The moment Rule 10 was introduced, I knew it was going to be used as a citation to ban for the pettiest of reasons — and could even tell the ban hammer on the horizon. I voiced my concerns, and they’ve proven my point.

I had had my share of “bad faith discussions” in public with the moderator in question before, so the ban came as no surprise.

My “sin” at that time was that I dared say, it’s not okay to discriminate against people on Github and go out of your way to close their issues/PRs just because they have an anime profile picture, and that enforcing Secret Guidelines in a passive-aggressive manner is not professional behavior (where “professionalism” was the argument against anime profile pictures, even on a public forum such as Github).

They’ve had me blocked on Twitter since, and that won’t change. Now that they’ve built their own framework of power, designed to keep me, “the /r/androiddev nitpick squad” out of the picture (Rule #10 V.. wait, it doesn’t applies if they do it), it’s clear that I won’t be able to return to /r/androiddev.

And honestly, why would I stick with a community that is represented by someone/people who create a blanket rule, usurp control over the community, go out of their way to find petty reasons to covertly silence others, either for criticism, or for disobeying some Secret Guideline they “forgot” to specify, with a complete lack of transparency (as transparency is also considered a violation of Rule 10 — asking for clarification is a violation, as it’s considered a personal attack on the moderator team).

Is this an open community? Is this a community I want to be part of?

Well I can’t be, but with these changes, I wouldn’t want to be. This is for the better. In fact, let me tell you this one nice fact: by banning me from /r/androiddev, and preventing me from spending time on helping people in their Weekly Questions Thread (sorry!), you all, my Readers here on Medium, get way better quality content and hopefully with more frequency than ever before! 😊

And don’t worry, there are other nice, helpful, and well-versed Android Developer Redditors on /r/androiddev who can help you in my (permanent) absence.

Developers who are not on the blacklist of the moderators, where the mods are in fact actively trying to find some justifiable excuse to pull out the ban hammer for months (and as they couldn’t, they’ve created their new rule system, with which any comment from anyone can be silenced).

In the meantime, I’ll be over at /r/android_devs, having free and open discussions on Android development just like before.

Till then.

(Special thanks for the help on moving the inline ViewModelProvider.Factory into createViewModelLazy to support nav-graph-scoped ViewModel injections with SavedStateHandle — as I had not been aware of &reateViewModelLazy used in this context.)

By the way, if you’re curious to hear more about these events, check out this live chat on YouTube discussing the events.

--

--

Gabor Varadi
Gabor Varadi

Written by Gabor Varadi

Android dev. Zhuinden, or EpicPandaForce @ SO. Extension function fan #Kotlin, dislikes multiple Activities/Fragment backstack.

Responses (9)