Monday, November 24, 2014

QML wrappers for native Android components

TL;DR It might be simpler than you think to wrap native UIs with the QtQml module, but it does require a good understanding of the underlying platform

In my previous post, I showed how you can work with platform-native classes and use the QtAndroidExtras module to create UIs from code. One of the downsides was the extra code complexity you had to manage. The "Qt way" of doing UIs is, however, by using QML, for the reasons of brevity, development speed and flexibility. I cobbled together a custom Android component module to demonstrate this (sources available on GitHub) - the 40+ thick line C++ UI example suddenly becomes a lot more readable and fun to work with.



Qt does have a solution for native-looking UIs on Android in the form of QtQuick.Controls, so my reasoning here is more to show

  • what are the advantages of custom components
  • how custom components are done
  • what are the pitfalls of custom component sets
What are the advantages? If there is no native-looking component set, it's quite clear - you simply have no alternative. If QtQuick.Controls does exist for your platform (see the case of Android), there is still a number of reasons why you might consider going custom. First, you get the full functionality and availability of the platform-native set. QtQuick.Controls gives you something that is closer to a lowest common denominator, with a lot of platform-specific components/functionality missing and occasionally some input method quirks. By skipping this, you create UIs which are not just "close-to-platform-native", but effectively *are* platform native. Second, you get the full performance. By having a wrapper, there will still be a slight penalty for loading Qt, and parsing/constructing the QML objects, but there is no compromise once the UI is instantiated. I personally also like that you can use the platform-native documentation and examples, since you're just using QML syntax, but class names, properties and general usage pattern remain the same as on the platform.

How does one go about creating a new component set? As you've seen from the first QML snippet, all we really need is the QtQml module. If you start creating with a completely custom *native* component set (ie not deriving from an existing QtQuick or QtQuick.Controls base) you get a really basic set of components to work with, namely Component, QtObject, Binding, Connections, Timer. All you native components will be actually QObject derived C++ objects, registered with the QML system, as described here. The classes themselves are still plain Qt classes, here's how my activity.h looks like:



After that, you need to register every such object with the QML engine (usually in your main.cpp) :

qmlRegisterType("OutQross.Android", 0, 1, "Activity");

With this we can start instantiating our custom objects from QML. The QtObject class/element unfortunately does not have a lot of the functionality normally coming from Item (including having a default children property), so I created an OutQrossItem helper class for this purpose, which is the base class for all my Qml elements.



Finally, let's see the gritty innards of a custom component class, an Android Button in this case



As you can see, I still use the same UI inflate paradigm as Android to actually instantiate the button (this way we also avoid the question of the order of object initialization or property settings), and you can also see how a property can we wrapped. This means that the full power of QML - bindings, signals/slots and JS are available to us as the properties are mapped to the native components. This is crucial if you are building your own component sets, as otherwise you're just making a syntax converter - and this is also the answer to how this is different to just converting QML to Android .XML UIs or vice versa.

For completeness' sake, let's mention that the .APK was 6.9MB and the memory usage of the HelloWorld app was 61510kb (about halfway between the version in my previous blog post and the "full" QtQuick experience)

There are some pitfalls as well. First, while not rocket science, it's actual work. If you have an extensive GUI widget set or library (like Android), you probably want to use or write some sort of wrapper code generator as manually creating and maintaining a full wrapper set can be daunting. Second, there might be specific UI caveats - for example, in the case of my Android example, to keep it simple, I'm not actually running on the UI thread. This means that in my particular implementation while native component and property updates happen via the bindings, they might not immediately appear on the screen. This can be resolved, but it does require a very good understanding of how the native platform works. My advice is that you shouldn't consider writing wrappers because you don't know how the platform-native UIs work - it should primarily be done to accelerate future development and prototyping.

Monday, November 17, 2014

Native UI in Qt on Android (without QtQuick.Controls)

TL;DR - Yes, you can do a fast, no-compromise native UI without QtQuick on Android, and you'll use a lot less resources than a full QtQuick app, at the cost of somewhat messy code.

Nowadays Qt for Android comes with a nice QtQuick-compatible set of native-looking Android controls in the form of QtQuick.Controls. This blog post is not about them :) Let's go a bit off the beaten path - can we create a Qt application with a graphical, performant, native UI without using QtQuick (or QWidgets)? What are the advantages and disadvantages to such an approach?

In a blog post Artem Marchenko lists a couple of approaches how a Qt based native-looking UI would be possible, but these all include QML in one form another, which is not quite what we're after in this particular case. Another interesting project is Ben Lau's QAndroidRunner which combines native UIs and QML. Here, however, we'll focus how far can you get without ever touching QML or QtQuick.

The key to using Android classes and resources is the QAndroidExtras module (it's an add-on module that has been around since Qt 5.2). The class I'm heavily relying on is QAndroidJNIObject which allows Java class/object instantiation and manipulation. Thus, the two includes we'll use to enable creating native objects are

#include <QtAndroidExtras/QAndroidJniObject>
#include <QtAndroidExtras/QtAndroid>

When it comes to Android UIs, the first stop is the Activity - on Android activities are how we interact with the user. This is effectively a window on which we put all our widgets and UI elements. Luckily, starting with Qt 5.3, we have a simple way of retrieving an activity with the QtAndroid::androidActivity function.

QAndroidJniObject activity = QtAndroid::androidActivity();

Let's get to the meat of the matter - we'll need to create native layout and widget objects and set their parameters from Qt. Here's how that looks like

QAndroidJniObject layout("android/widget/RelativeLayout", 
  "(Landroid/content/Context;)V", 
  activity.object());

The first parameter is the Java class name, the second the Java method signature (see here for a few more examples, "javap -s" is your friend), and the third parameter is the actual object used as the parameter (as activity is a QAndroidJniObject, we need to use the object() method). With the call above we create a Layout and assign it to the activity.

We can also call methods of our objects:

button.callMethod<void>("setTextColor", "(I)V", -1); // Color.WHITE

Putting all these together, we can create a basic, but full-fledged Android application:



...resulting in...


This "Hello world" code is clearly quite a bit more complicated than it's QML counterpart:



The complexity downside is quite obvious - it's hard to beat QML's brevity and clarity. Another, perhaps not immediately visible downside is that this particular style of C++ code is more error-prone - there is no type safety as the objects are generated dynamically, so on errors, it's easy to end up with NULL objects and segfaults.

Why would anyone use it, then? Let's take a look at resource consumption:

The APK size for the non-QML Android version of Hello World is 5,663,420 bytes, while the APK with the Controls included is 10,406,706. The difference could be even bigger, though, as the default android mkspec includes a few extra Qt modules. It should be possible to get the minimum APK size down to around 3MB. If you are using Ministro, this might not be as big of an issue, but for self-contained applications, or embedded, this can shave a few precious megabytes off of the application.

It's not just flash storage and network bandwidth we can save though - there is a memory-usage difference as well. While adb dumpsys meminfo is not a perfect way to measure memory usage, it is indicative:

40761 kB: org.qtproject.example.QtJavaHelloWorld
77531 kB: org.qtproject.example.QmlHelloWorld

While a ~35MB of minimum framework tax might not sound like much in the era of devices with several gigabytes of RAM, using a complex QML structure can inflate the difference further. On embedded and legacy devices every megabyte counts, so even this 35MB difference can help (plenty of low-end Android devices with 256-512MB of RAM).

There are other benefits to not using QtQuick controls - controls are a "lowest common denominator" approach designed to easy cross-platform development. It does not contain all UI widgets and elements, nor functionality offered by Android APIs. By using the approach as demonstrated, there is no compromise - the full UI arsenal is at our disposal, at the exact same performance as for regular Java apps.

Finally, the QtQuick controls version depends on the Qt version shipped with the application. In the non-Controls version we always get the native controls, with native styling and behavior, even if the platform release is newer than what our QtQuick.Controls version supports.

To summarize, the advantages to a Controls-less approach are:
  • Native UI performance
  • Smaller APK size (currently at least ~5MB less, potentially ~7MB)
  • Smaller memory footprint (35MB for Hello world, more as app complexity increases)
  • Full UI functionality available, regardless of Qt version
  • Styling always latest platform-native
Disadvantages

  • Not cross-platform
  • Significantly increased code complexity, especially with more complex UIs
  • Harder to debug due to dynamic nature and lack of tooling support

It's actually possible to mitigate some of the disadvantages, so stay tuned for further posts on this topic!

Saturday, July 9, 2011

Qt Components - The story of the ugly QWidgetling

Disclaimer: these are my own, personal thoughts, not necessarily matching those of my employer, neighbor, goldfish or innocent bystanders.

Paradigm shifts are always difficult, especially when linked to technology changes. Qt Components are here and seeing that there and at least three component sets (MeeGo Harmattan, Symbian, MeeGo UX) and counting, it is hard to see how components will help solve the cross-platform problem that has been plaguing mobile developers. Hard to see, if you think about Components as a QWidgetling, that is. In the vein of the story of the ugly duckling, I will try to explain how you can utilize Components and avoid the feeling of being given a square peg when you are looking at a round hole. To repeat, this is about figuring about just what QML with Components is, how it's supposed to be used. Disclaimer #2: Those who think QWidgets should have been the long term technology of choice, or hate QML with a passion, please skip this article. You're not going to like it, and I'm usually delete-button-happy, so save the angst and skip it. Sorry and thanks.

QWidgets

If I was trolling, I would say they were (mobile-wise) the cross-platform solution that never existed - it certainly *sounded* good that I, as a developer, work against an API and the UI framework works out everything, resolution, input method, hardware be damned. This, however, is a bit like listening to politicians - just vote for me and I will solve all problems. Well, not quite. As many have outlined, the paradigm shift from the "I wrap everything to native controls which
 kinda look/work the same" characteristic for the desktop spelt doom for the classic widget based approach. I have gotten nasty looks IRL for saying this, but Maemo5 did not prove QWidgets is the solution - it proved that it is NOT (yes, I be trollin', but only to make a point :). It was a great effort, and while the looks were certainly on par, your supposedly cross-platform code was a mess, full of #ifdefs, modules and classes specific to Maemo5. As I sometimes say, well, with enough ifdefs, you could switch between GTK and Qt, right ? At the same time, development was not made any easier - there were no specific UI tools for creating Maemo interfaces.

QML, and the long road to Components


Enter QML, a radically different approach. As people soon realized, QML is a base technology, it does not provide you with buttons or much higher level functionality you previously had with QWidgets. Also, at the same time, it was said Qt Components will solve this. All was well, it was just a little wait and we will have our new incarnation of QWidgets, right ? Well, not quite...
 
QtComponents have hatched...


... and it turns out they harbor a pretty ugly QWidgets replacement. People trying to make QML that runs equally well on Harmattan using the Harmattan components, Symbian using Symbian components already felt this is not good. Recently I got a contribution for QtInfo in the form of a cool Symbian components UI. I tried to kick it around a bit to make it work on Harmattan, with bleak results, with projects diverging already at the HelloWorld. Something was wrong. This duckling was really ugly. After a few days, however, it dawned on me...

A chainsaw cuts trees faster than an axe only if it's on

The reason I was suffering and losing time was because for years I have been taught to think (much in the vein of QWidgets) almost axiomatically that I want a single codebase and with enough tweaking (ifdefs) it would run equally well on platforms. That is no longer necessarily true (if you think I'm losing my mind, that's understandable, but read on). In terms of the title of this paragraph, I had Components (the chainsaw) and I was hitting the tree with it like I would with an axe, and it really is a horrible experience. However, there is a paradigm shift on the horizon. Turns out, Components solves the cross-platform challenge NOT by providing a singular API. It attacks the problem by making is super-easy to write throwaway code. WHAT ? (if you think I *completely* lost my mind, that's understandable, too, but read on).

Live fast, die young

One of the key appeals of the old widget approach was their enduring nature. If you wrote a desktop application 10 years ago with it, it would likely work okay today. However, this age of UI tranquility, taken for granted for so long, is in it's final days. As we've seen, today even the most conservative desktop OSs are undergoing UI paradigm shifts. Mobile is even faster, 4 years in mobile is an eternity. Writing an UI as if it was there to stay for decades is losing appeal quickly - the life expectancy of mobile UIs is dramatically shorter, which coincidentally also lowers the long-term maintenance burden, traditionally so important to developers.

Use the Force. Let go, Luke.

As Ben Kenobi said. And even though it seemed weird to the people in the rebel base for Luke Skywalker to use the Force instead of the targeting computer, it shows us what's happening here. Let go of the idea of monolithic, single UI codebases. Let. Go. Like the huge armor of European medieval knights on appearance of the guns - the armor no longer provided real protection, but made you slow and unmaneuverable. So, what should I do, I hear you asking ? EMBRACE DIVERSITY. Use the weapon/components best suited for the platform. Don't be afraid to improvise or include your own Elements, or borrow (barring license limitations) from Components of other platforms. Keep your UI (QML) layer as thin and nimble as possible, keep the logic on the C++ side, it will help you both in terms of portability and speed. Spawn as many UIs as you wish. Think about the UI as a throwaway component. Is it Harmattan ? Okay, I'll just drop in the QML from the examples, who cares if it runs on Symbian. Different resolution ? I'll just include a different QML optimized for that. I can see the disbelief in your eyes. How can this work ? The point is that spawning UIs with QML is *fast*. Really fast. Actually way, way, way faster than tweaking for a certain subset/common API or resolution independence. You will write (and maintain !) 5 different UIs *faster* than you would write a monolithic QWidgets UI that would work truly cross-platform. It is a radically different setup. Returning to my QtInfo porting example - it took me nearly a day to create a port from the Symbian-only version to a version that ran on both Harmattan and Symbian, but it meant a lot of compromises and visual artifacts, rotation and integration issues. However, when I realized the new paradigm I outlined above, I had a sexy Harmattan fully integrated native UI up and running in 30 minutes, just appended the QML to the package and now the application is cool on Symbian, Maemo, Harmattan, the Desktop, and I could add a tablet MeeGo UX in another 30 minutes.

There you go, with all the acknowledgement that this might be hard to swallow for many of you, trusty old QWidget veterans, hopefully I now explained just what kind of square peg Qt Components I see and you will hopefully stop trying to shove it in that round hole :) Now go and make Qt Components shine and create beautiful swans of Harmattan, Symbian, MeeGo, or whichever platform the Qt winds might blow on.

Sunday, April 10, 2011

Is Open a dirty word now ?

Open technologies gained a lot of momentum in the past couple of years, but what became of the term Open ? Does it still hold and mean the same values it did previously ? Were the purists speaking about differences between Open and Free right ?

If you're reading this blog you're probably aware that I'm fairly fond of FOSS, both as a concept and a business model. Undoubtedly, open technologies made huge inroads in various industries - servers in the IT industry, various OSes in the telecoms industry, etc. However, there is a disturbing tendency going on in the world of Open. Meaning that Open implies Free less and less. You might be surprised at this point - what ? How can you be Open and not Free ? Well, if you look at the 'classic' FOSS definition on Wikipedia, you'll see Open Source is defined as "An open source license is a copyright license for computer software that makes the source code available for everyone to use." - this is the type of open we all cherish and love. But let's take a look at who sports the Open sticker nowadays ? The 500lbs gorilla is of course the Open Handset Alliance, and the Android Open Source project. Despite the lot of "Open" in the names, in the open is not where development happens, and lately even the source availability became selective. Another infamous example is the Open Screen Project run by Adobe intended to bring Flash related technologies for everybody - but in fact being nothing more than another industry alliance to try and grab market share from competitors, Open Source and values not accounting for much. There are other organizations that also ride the Open moniker - let's take for example the Open Design Alliance - a nonprofit organization that focuses on development of CAD-related libraries... However, open here means pay for the binaries, and pay (a lot - 25K$) to see the sources, unless a board of members deems you worthy. Not the type of open libraries you were expecting, right ? That's where the problem lies - Open is more and more a sinonym for the "For Sale" sticker, and the term "Alliance" is becoming a marketing lingo equivalent of "Cartel". This problem was recognized fairly early on by OSI and hence the term OSI approved Open Source license, but if the term Open becomes diluted enough, will it even mean anything except for marketing purposes and a counter-term for "exclusive technology" ? Will the O in FOSS become meaningless ? To be honest, the term Open was used long before Free, before the software industry, and it meant "public", mostly in the context of standards. However, now even that, decades old meaning is losing it's value. Today's Open is far too open unrelated to Free or even Public.

What's the takeaway, you might ask ? Be careful of how you yourself use the term, and be vary of supporting any project or (especially big business) initiative just because it uses the word "open". Support the values, and projects that promote those values, not the names.

Saturday, April 2, 2011

The age of pointless mobile benchmarks

Mobile devices are ever-more like their counterparts and we're starting to see benchmarks popping up on popular gadget sites. Sadly, most of these sites have not yet gone through the analytical phase of what most PC review sites did many many years ago and if effect turn into simple marketing vehicles. Let's see the common pitfalls !

To be clear I'm not talking about bad journalism, that result in articles like this (note how they manage to leave out from the title the ONE thing - GPU performance - that the article is about) even if the source a very reasonable article. I'll try to describe the differences why the methods and criteria established in the PC field that are starting to be applied to the mobile arena (like the Anand article above) make a lot less sense than they did for the original personal computers.

Pitfall no. 1: Level of integration - hardware

Mobile devices are nowadays mostly built around what is known as a SoC (System-on-Chip), which means that most components that would be separate chips or even replaceable units even on a notebook computer, are all rolled into one, which makes it very difficult to benchmark *single* components as they are inseparable from the rest of the system. How much is a NVidia vs ATI benchmark worth if you're comparing a Dell Inspiron 13" notebook with a NVidia card against a HP desktop one with an ATI and a 22" display ? To make it worse, some of the devices include additional video processing units (which again might be good only for some formats and bitrates) so the original chipset-style comparison is even more difficult to make.

Pitfall no. 2: Architectural differences


X86 CPUs is incremental - hardware backwards compatibility is pretty much a given, the only difference in capabilities is the in reality only in the timeframe when vendors add in extensions (like the different iterations of SSE). Mobile devices have to be a lot more transistor-count an usually binary compatibility is the first one to go. For example the relatively new Tegra2 chose to forego the NEON multimedia instructions that were common in Snapdragon and OMAP chips. OpenGL ES 2.0 is not backwards compatible with OpenGL ES 1.1, etc. Depending on what you use and how your software works, this makes things a lot murkier as the fact that device X works better with Quake 123 means nothing in terms whether what you want to run works better.

Pitfall no. 3: Form factor difference

A variation of the previous point - the resolutions are vastly different, but this does not only touch on the graphical performance, but also on our usage patterns and perception of performance. Nominally a device can have a better framerate, but because of screen size and distance-to-eye differences you might actually subjectively prefer the one with the marginally lower framerate.

Pitfall no. 4: Multicore is a sword with two edges

Any multicore design is only as good as the software that runs it. This was true on the desktop, but multicore is a whole different beast on mobile devices, as the quality of software determines whether that dual-core monster is better or, actually, worse than a well executed singlecore one, both in terms of performance and battery usage.

Pitfall no. 5: Software platform diversity

In the desktop and notebook world, just by taking Windows, Mac (and Linux of course ! :) you cover 99%+ of users. On the other hand, in mobile you literally have a jungle of OSes (there are almost a dozen OSes that are above the million mark) which are optimized for very different hardware and very different use-cases. This is not an issue in the X86 world because the application-level performance is comparable with at best a few percent difference, but on mobiles, due to drivers and being optimized for various modes of operation, the same HW can behave vastly differently with different OSes, so those extra FPS in a benchmark can again be very misleading.

Plenty more pitfalls, but let's not overdo it, so for the conclusion - since the complexity of these devices now rivals personal computers, it's only normal that people want to compare them the way they did with personal computers. The trouble is, these are not NEARLY as uniform as their bigger brethren so new ways of benchmarking will have to come up that will be less hardware or device oriented even if that means we'll have to give up FPS measures as such.

Wednesday, September 22, 2010

The Maemo Community Council elections - Why you should care


The maemo.org community is something of a rare bird in the mobile environment - a self-organizing community centered around Nokia's tablet line, with a very strong infrastructure and unique spot where users, developers and hackers come together resulting in a lot of synergy and powerful base compared to the niche aspect of Maemo. One of the most peculiar aspects of it is the Community Council, a representative body that deals with all things Maemo. What it can do, why should you care and, most importantly, why should you vote *TODAY* if you are a community member with enough karma ? Read on to find out ! I also hope this to shed a little light and be a short introduction of the inner workings of maemo.org to people NOT familiar with the intricate community workings of maemo.org

The Community Council was formed two years ago as a representative body, with a role to "represent the Maemo community's best interests to Nokia, and to act as a community conduit for Nokia-generated information." This role has recently been expanded, as Nokia has transferred control of maemo.org to the community (=the Council as a representative body). This gives it a fairly complete control over all things that happen in maemo.org, a lot of which many users are not even aware of:


  • the rules that define criteria for software in extras and related repositories
  • determining forum policies
  • overseeing collection of documentation/wikis/howtos
  • ability to issue tasks to maemo.org staff (you DO care if the forums or Extras breaks down or is difficult to use, right ?)
  • disseminate important information through the Council blog
  • help coordinate community events (Summits/Conferences/meetups)
  • help with grassroots efforts (competitions, hackfests, anything Maemo)
  • formalize concerns and issues and pass them to Nokia
  • define goals/strategies for the future of maemo.org
  • really, anything maemo.org related
  • in fact, anything community related goes - it it's cool, support it !

So yes - if you would like more of the things above, or you have a peeve you'd like changed - the Council is the body whose attention you need. Generally - if you have an idea relating to Maemo, and you don't know who to turn to, the council is the address (with a notable exception - the Council is not Nokia Care, Nokia has proper customer care channels, the Council focuses on community aspects).

Even with fairly complete control over the maemo.org realm, I cannot emphasize enough the facilitation character of the Council when it comes to Nokia. Nokia does not obey the Council - but certainly does listen to it as it represents the most vibrant and passionate base of it's users.

I've listed a short (far from complete !) list of what the Council can do - but there are things, often sources for confusion, of what it CANNOT do. Can the Council guarantee that a particular firmware will be made available to your device ? No - it's not a community call. Can the Council force Nokia to include/exclude components from firmwares ? No - it's not a community call. 

Some of you Maemo device owners will stop (or already stopped with tldr ;) ) reading as they don't really care about this maemo.org stuff (something I doubt, as Extras, a central part of it, is the main source of applications).

However, if even if all this community-talk jibberish means nothing to you and and you define the bottom line as Apps Apps and Apps (with Flash), you should pay attention. The Community Council is in the unique position to make a strategy for the future of the community. It *does* direct the work of maemo.org people and will be pivotal in determining for example what software will be available from MeeGo repositories, or just how difficult it will be for MeeGo/Harmattan developers to support the Extras repository. They can (and IMO should !) also be the voice of the Maemo community inside MeeGo - MeeGo doesn't have a council, and if you have an itch, it won't get resolved unless you or your representatives do it.

All this brings us to the bottom line - in my opinion, the Council matters, but only to the extent of the abilities of people in there. In a worst case scenario, the people you elect to it do nothing - that's the same as if it didn't exist. However, YOU, the community member have the power to elect people who ARE capable of affecting the future of maemo.org and all the people and device it touches on, be that apps, events or other. Today is the last day of the community elections for the next term, so if you care the tinyest bit about Maemo, MeeGo, maemo.org or your Maemo device - use that voting token and make a difference.

Saturday, August 28, 2010

Java mobile apps today - a technology/weapon misunderstood

When you look at the present landscape of available mobile technologies to develop apps with, you'll see an abundant offering. Depending on what platform you're developing for, you'll see a mix of native applications, web technologies and similar. These are all cool and powerful, but what is not often talked about is the scope of the applications written and how choice of these technologies is linked with business models. What am I talking about here ? People working with J2ME will be eager to point out that there are several hundred million phones capable of running such apps and that phones that have that as the primary application platform are still going strong (for example Nokia's S40 phones). Others will say that means nothing  as owners of such phones are unlikely to pay for apps and that their hardware is so limited that you couldn't make something impressive anyway. Both are right in a way, but the story doesn't end here. What is often overlooked that shiny, flashy apps, sold as a product are not the only business model here even though originally most of the J2ME offering were just that - games. Think about SMS for a moment here. It is a super simple service, basically a specialized application, that earns huge amounts of money. But is the money in selling this 'SMS application' as it would be in selling 'Angry Birds' ? No, it's in the services it supports. It's somewhat similar to the problem that some people first encountering Open Source business models have - if you give your app away, how do you make money ? And this is why Java mobile app programming of today is misunderstood - it should not be viewed as a competitor to the write-app-sell-app-...-profit business models of iOS, Android or Symbian, but as a gateway that allows addressing a huge user base otherwise currently unreachable with other technologies. You don't need super-high technologies and sophisticated devices to check for a public transport schedule, arrange reservations, inventory/availability checks, etc. In that light, it's far from dead and/or useless. It's just about realizing that you have a business that CAN benefit from such a userbase.