Skip to main content

· 2 min read
Nigel Maynard

Background

I chose Bloc as the state management solution for Quiri after a lot of reading into the different options because I felt that it had less "magic". It also had twice the Github stars of the next most popular project and seemed to have the support of larger teams. Since choosing it there have been some challenges.

  • side-effects and one-time events like page navigations and toast messages
  • streaming data from the matrix client

Streaming from the Matrix Client

I go into more detail on how the matrix Dart SDK syncs data with the server and exposes in the [Listening for New Messages With the Matrix SDK](2024-12-19-listening-for-new-messages-with-the-matrix-sdk copy.md) article. Ultimately the SDK already maintains the state of all events, etc. that come from the Matrix server. What the SDK exposes to Bloc then is a state object and a collection of streams that can be subsciribed to that provide all event details. I have been subscribing to the matrix streams in the Bloc and then adding events to the bloc for each new emission from the matrix stream. This article from the Bloc team presents emit.forEach as an option for subscribing to reactive repositories instead. They show the same pattern here in the docs.

· 2 min read
Nigel Maynard

Background

Almost all actions in matrix are expressed as users sending events to rooms. This makes intuitive sense when it comes to message events but works equally well for non-message events (e.g. add user event, change room name event, etc). When a user adds an event to a room, it is first added to the event graph on their personal client, then it is replicated to the user's home server and then it is replicated to all other clients that are in that room. Quiri is written in Dart and uses the matrix dart sdk to handle a lot of the client-server relationship. This includes maintaining the local copy of the current state of a room's state and listening for updates to that room's state. This guide goes into details about how that relationship is maintained so that we can build the quiri client appropriately.

Listening for events

On app startup, we currently call the init method. This method does a number of things including kicking off the first _sync request.

This sync call can be awaited as a whole, or you can provide the waitForFirstSync as false and instead await the roomsLoading, _accountDataLoading and userDeviceKeysLoading futures individually. We will await on the init for now for simplicity.

After the initial sync, the client will begin a _backgroundSync that long polls every 30 seconds (configurable).

Replicating the state in the client

Each sync job uses the _handleSync method to perform the appropriate updates to state.

For example, in the _handleRoomEvents sub-handler the updates are made in the following order:

  • the in-memory state (accessible from the SDK client object e.g. client.rooms)
  • the client side database for persistent storage
  • the SDK's exposed event streams

Reacting to new events in the UI

In particular, how do we know when new messages have arrived? Let's look at how Fluffychat does it. First let's look at where they render their chat boxes. The messages are found in a room's timeline. The timeline is returned from the SDK getTimeline method. An onUpdate callback is passed to allow the UI to react to new messages arriving. Here is where Fluffychat sets up the timeline listener for new messages

· 2 min read
Nigel Maynard

Background

Inviting someone to a Quiri is a core experience in the quiri app. This guide studies the matrix invite functionality as well as how the Dart SDK implements it (with references to how Fluffychat does it.) See User Discovery in Quiri for a discussion of user discoverability and maintaining a contact list.

Accepting an Invite

Lets see how Fluffychat does it. It appears that when you are invited to a room, you will be added to the room but with a membership status of invite. The SDK calls the join room endpoint. Fluffychat is then SDK's client.waitForRoomInSync method to wait for that room to be enabled after the room join event is added to the event graph.

TODO:

  • how do you list rooms that you have been invited to?

Listening for Invites

The SDK offers the waitForRoomInSync method that waits for a specific room by ID to experience an invite, join or leave event. For private rooms I don't imagine that you would know the id of the room to be able to listen for it in this way. I haven't figured out how Fluffychat handles room invites but based on the waitForRoomInSync method I am watching the onSync stream, filtering on room events.

· 6 min read
Nigel Maynard

The Problem

In the current dev build of Quiri, the only way to open a conversation with a user is to know their username and to enter it perfectly in the conversation partner field. Here we explore ways to improve this experience, including:

  • inviting a user via their email or phone number (3rd party identifier / 3PID)
    • inviting users without an account to create an account to accept the invite and start the conversation
  • a contact list or friends list to quickly open new quiris
  • adding a friend using their quiri userID or displayname

For the MVP all rooms are to be private and 1-1 and there are no plans to help users discover one another on the platform (e.g. no public spaces for posting). Public posting is being considered for a future release.

What Matrix Offers

As we are building on top of matrix, we should first attempt to leverage the features and patterns that they offer out of the box to minimize custom dev work.

Inviting a known user

This is the flow that we are using in the dev build today. Matrix users come with identifiers that look a lot like emails (e.g. @nigel:quiri.io). When you want to open a room with a user you can address it in the same way that you would address an email: know the user's email exactly and write it correctly. If the address is wrong, the room will fail to open same as an email would fail to send.

Matrix servers offer the ability to search the user directory for other users by their user ID or display name. This feature does not simply list the users though. Some search term must be passed (e.g. at least a single character like a) and only those users matching will be shown. As such, this is a feature that could help users find each other in two cases:

  • the quiri userId/displayname was shared previously and the search provides some confirmation that the user actually exists and may provide suggestions if the userID/displayname was misspelt
  • A user can try their friend's real name and hope they found the right person if the search returns a hit

Configuration Options

The synapse matrix server implementation offers some configuration of the search results but it is limited in it's configurability.

At it's most open, it returns search results for all users on the server and other servers. For the MVP this may be acceptable but as the community grows it could enable spammers to abuse the platform and harass random users.

The one restriction that is offered is to set the search_all_users property to false in which,

"...search results will only contain users visible in public rooms and users sharing a room with the requester." With this flag set we might be able to use this feature to enable searching of all users you have interacted with before as long as you are still in a room with each of those users.

Third Party Invites

Matrix offers a way to invite someone to a room using a third-party invite. If the user already has an account associated with that email/phone then they are added to the room. If they don't have an account yet, the user is sent an email inviting them to join quiri (and the room they were invited to). Once they have created an account associated with their email they can join the room.

List all users in a room

For a given room, all members in that room can be listed. this does not help us with our problems directly but may be useful as a workaround for storing a contact list.

Search a room

Matrix offers the server-side indexing and search of room event contents. This also does not help us directly but could enable the searching of a "contact list" room.

Our Solution

Inviting someone to a room using their email

The built-in third party invite functionality appears to handle this. A more thorough audit should be performed against a local instance of Synapse.

Contact list

Matrix does not currently offer a contact list functionality. If we want this functionality, the out-of-the box solution will be to create a non-chat room for each user in which only their contact list is stored. Non-chat rooms are an expected use-case for Matrix so it's not terribly hacky but the functionality will be limited. The room search feature could possibly be used for filtering the contact list but sorting will likely be client side. The client will handle the logic for when a user is added/removed from a "contact list" room and will have to be careful to not include the contact list room in the quiri list. A more thorough audit of the room invite functionality should be performed to detail how "contact list" room invites and acceptances would map to "contact list" invites and acceptances/rejections.

Adding a friend to your contact list

For the MVP there will be two ways in which you can add a user to your friends list:

  • knowing their email
  • knowing their username or display name

This feels quite restricted but making user directories searchable trades the convenience of searching for users by name with the risk of users being spammed. While this is a low risk today, as the platform grows, this is the kind of thing that crushes communication platforms.

Taking Discord as a reference, they offer adding a friend strictly by username but you can add someone to a server by email.

Conclusion

Overall, the user experience will be:

  • open conversations for the first time by providing their email (or knowing their username)
  • a one-time use shareable add-me-as-a-friend link that can be pasted into an outside chat or email would be a nice way to make friend adding easier
  • open further conversations with a user via your contact list

· 3 min read
Nigel Maynard

Matrix SDK Sessions

Matrix uses access tokens to authenticate user requests. The Dart SDK handles a lot of the token management for us.

For example, when setting the user's display name, there is no way to pass the auth token. This is because it is stored in a class variable and included in the request on our behalf.

But what happens to the access token when we shut down the app? The instance of the SDK class will be disposed and the token will go with it. This is why the Matrix Client can be provided a databaseBuilder when being created. The SDK will store the access token, amongst other things in this database and fetch them on startup. This is what will allow us to keep users logged in even when the restart the app.

There remains the question of how long a client can stay logged in. It seems that the default for FluffyChat is to have long-lived access tokens. But the matrix spec allows for the use of refresh tokens for improved security.There exists a method refreshAccessToken() that refreshes the access token but it appears that FluffyChat doesn't use it... Looks like I'll need to schedule a task to refresh the token.

Other session data

While the matrix SDK has it's own ways to persist session data, there are also cases in which we will want to persist session data (e.g. the user's sign-up stage). For these non-matrix session data, we will use the Hydrated Bloc package to securely persist the session data.

State Restoration in Flutter

Thorough blog post

  • maybe use this primarily for navigation state and lean on hydrated bloc for other state?
  • goRouter state restoration example
  • might need to use statefulShellRoute?
  • state restore is only for going from background to foreground if the OS reclaims the memory because it needs it
  • when manually shutting down the app and restarting it, state restore does not work (at least for go_router)

Startup State Restoration

  • If a user has never used the app
    • there is no state to restore and they should land on the landing page
  • If a user has started the signup process, but has not yet registered an account (they are registered after picking their unique handle)
    • return the user to where they were in the signup process
  • if a user exits out of the signup process before registering via the close button in the app
    • clear the app state entirely (including any signup progress)
  • if a user registers an account successfully
    • clear all signup state (they are signed up now, so we don't have to go back!)
  • if a user logs out after logging in
    • clear all state
  • if a user logs in and they have not set their avatar or display name (last step of signup that occurs after registration)
    • detect when loading homepage and redirect user to create profile page
      • using route guards?
      • create profile page needs a logout option
      • the display name and avatar can be attached to a User HydratedBloc so that we can cache the avatar and displayname

· 6 min read
Nigel Maynard

For Quiri, we want to register users with a username and an email. Luckily, we can reference how Fluffychat does this!

Choosing an avatar and username

Provide email and choose a password

Lets find the associated REST calls in the matrix API spec

Check if username is available

Matrix spec

  • no auth required
  • fluffychat doesn't use this maybe because it doesn't reserve the username?
  • should be useful for username selection screen

Register

SDK code | Matrix Spec

  • no auth required
  • when hitting the matrix.org server with only a username, it returns a 401 with a response body that tells you the "authentication flows" that are available to you for this endpoint on this server.
{
"session": "jgIeMOlHYubxOZYzaFSMCFqJ",
"flows": [
{
"stages": [
"m.login.recaptcha",
"m.login.terms",
"m.login.email.identity"
]
}
],
"params": {
"m.login.recaptcha": {
"public_key": "6LcgI54UAAAAABGdGmruw6DdOocFpYVdjYBRe4zb"
},
"m.login.terms": {
"policies": {
"privacy_policy": {
"version": "1.0",
"en": {
"name": "Terms and Conditions",
"url": "https://matrix-client.matrix.org/_matrix/consent?v=1.0"
}
}
}
}
}
}

In this case the "authentication" is the steps that you will need to go through in order to register on this server; pass recaptcha, agree to terms and conditions and go through an email verification flow (receive an email and click the button in it)

On my local dockerized synapse instance I wanted to start with the simplest configuration and build up from there.

The basic configuration does not allow any registrations. Adding enable_registration: true will get you a startup error telling you that you shouldn't allow registrations without some additional measures to confirm identity (like the steps above that matrix.org makes you go through). Otherwise people can spam requests to create accounts.

On my local though, it tells me that I can add enable_registration_without_verification: true to allow such registrations anyways.

After doing that I naively try to submit a registration request with just a username and an email:

curl --location 'localhost:8008/_matrix/client/v3/register' \
--header 'Content-Type: application/json' \
--data '{
"username": "cheeeeky_monkey",
"password": "password"
}'

I get back this cryptic response and it does not register a user.

{
"session": "bFygXrPrCVAOztdAJyjjDOim",
"flows": [
{
"stages": [
"m.login.dummy"
]
}
],
"params": {}
}

After reading through the User-Interactive Authentication API docs I learned that this is my server telling me that I have one flow that I can take to authenticate this registration request. And that path is to perform no flow! All I have to do is add the auth parameter to my registration request.

{
"password": "ilovebananas",
"username": "cheeky_monkey",
"auth": {
"session": "qRFAzYnVJgHQdobMkUhOQlUU",
"type": "m.login.dummy"
}
}

The session is optional in this case because the flow only has a single step (multiple step flows will require sending the same session token). Including the m.login.dummy auth type basically is just a little hoop we have to jump through to make this request without going through any actual flow.

We registered a user! But as per that earlier error's advice, we can't go public with this configuration. For Quiri we want to register users with their emails so it's time to figure out how to configure the server to require email for registration and also configure it to send the emails!

Registering a user with an email

Setting up a dev email server

At first I was looking into different services like SendGrid (will probably use this for the actual deployment) but then I realized I should be able to use an SMTP server hosted in a local docker container!

So I am trying smtp4dev

Get the registration token

curl --location 'localhost:8008/_matrix/client/v3/register/email/requestToken' \
--header 'Content-Type: application/json' \
--data-raw '{
"client_secret": "monkeys_are_GREAT",
"email": "alice@example.org",
"next_link": "https://example.org/congratulations.html",
"send_attempt": 1
}'
  • the client secret is any string invented by the client
  • this sends the email with the validation link!
  • the response is an sid
    {
    "sid": "WurCLwjxFYIGiTIz"
    }
  • you cannot continue with registration until that link is clicked
  • go to localhost:5005 for the smtp4dev interface and open the email
  • the link is currently https, which won't work
    • I should figure out how to make it use http for the link
    • for now just copy paste it from the email into the browser and edit to http
    • it will redirect to a 404 page because I haven't set up a redirect page yet
    • but it did succeed!

Complete the registration

curl --location 'localhost:8008/_matrix/client/v3/register' \
--header 'Content-Type: application/json' \
--data '{
"device_id": "GHTYAJCE",
"inhibit_login": false,
"initial_device_display_name": "Jungle Phone",
"password": "ilovebananas",
"refresh_token": false,
"username": "cheeky_monkey_email",
"auth": {
"session": "bdZxZeFmpPxdAuSVoGalmiPC",
"type": "m.login.email.identity",
"threepid_creds": {
"sid": "ygxQvHncMIMQzFTk",
"client_secret": "monkeys_are_GREAT"
}
}
}'
  • the sid is from the previous email registration request
  • the client_secret was provided in the previous email registration request
  • this will fail if the user has not yet clicked the link in their email
  • if they have clicked that link, the user is now registered, your first token is returned and we're off!

Email Registration User Experience

  • User is asked for email and password
    • random client_secret is generated by client and stored in localstorage
    • request is sent to matrix to send verification email
      • sid of response is held in local storage
    • password is held in local storage?
      • security risk?
  • User is taken to a page informing them that they must click the link in the email to continue
    • include button to resend the request
  • when the button is clicked in the email, we should try to send them into the app?
    • or just give them a message that they should go back to the app to continue
  • whenever they return to the "check your email" page, try to register them with no password
    • if they have yet to have validated, it will respond with EMAIL_NOT_VALIDATED
    • if they have validated it will respond with NO_PASSWORD
  • once the NO_PASSWORD response comes up, move the user through the username selection process
  • once they have selected a username, send the final registration request with username, password, email token (sid)
    • an access token will be returned and the user is on their merry way
  • make sure to remove the password from local storage!

Throughout the above process, if the user closes the app and re-opens they should be brought back to the screen that they were at. The information that they provide needs to be stored in "localstorage" until they make the final registration call.

· 4 min read
Nigel Maynard

We are now working on the designs for the MVP app which has me figuring out user signup/signin. While the matrix folks have built-in a variety of authentication methods, I am inclined to use a dedicated signup/signin service and let matrix focus on chat.

This post is my effort to lay out my understanding of the options that Synapse offers and the tradeoffs they have.

Research

Hosting My Own Hydra OIDC Provider and Using Already Built-in OIDC to Authenticate All users

It seems that while the proposal to have all matrix clients authenticate with matrix via OIDC, it is still just a proposal and is currently experimental. I feel that I now understand how I can have users that are managed by Ory Hydra/Kratos but have accounts on my Matrix server.

  • Kratos offers signup/signin services via API that can be used to create accounts
  • Hydra provides the OIDC interface so that users can "login using Ory" in the same way that they might "sign in using google"
    • while google has other applications and things that you might use your google account for, my Ory deployment would only be holding their identities (not the best explanation)
  • on the Matrix server side, I configure the server to only allow auth through oidc and only configure Ory as the provider
    • google, auth0, etc are able to be configured as downstream providers to Ory
  • if the proposal takes off, hopefully it's not too complicated to shift to using the Hydra/Kratos as the OIDC Provider rather than MAS

To Investigate

  • can we obscure the Matrix server to Ory auth process in a way that makes the user feel like they are using a basic login and not using a social-like signin?
  • how hard is it going to be to integrate Kratos and Hydra?
  • Is this overcomplicated?

Using PasswordProvider Module to Directly integrate with Kratos

  • does this restrict the login/security options that Kratos offers?
  • does this reduce the complexity that much?

Just use the built-in auth as it is

  • limited to the auth options that are already made available
    • there are a bunch of them...
  • less secure?
    • another auth rewrite from a team that should be focussing on chat?
    • they are very security focussed though...
  • looks like I'm going with this because the Kratos/Hydra integration is not looking straightforward enough at this time
    • in the future that does seem like the best option

· One min read
Nigel Maynard

After some exploration of Android development (specifically jetpack compose), I finally took a proper swing at Flutter and while jetpack compose seems like a powerful tool and I would love to get to know kotlin better, the cross-platform development and cross-platform design was just too alluring.

I figured I would start with a login screen as it would get me interacting with state and making an API call. I found some widget online that gave me most of the UI, leaving me to figure out constructing the login API request from the form inputs.

At first I was wary of using an SDK that was not created by the Matrix team but the Famedly folks are actively contributing to it. So the first thing was adding the SDK as a dependency.

It is also important for me to start getting to know the matrix API in general so I found their API reference.

· 3 min read
Nigel Maynard

After putting quite a bit of time into getting Element for Android working as a library so that I could wrap it in my own package, I was finding that even writing a simple feature was proving difficult. I did some evaluation of the amount of energy that would be required to get up to speed with android in a way that would be required for me to make any meaningful progress on quiri and Flutter started to look more attractive.

With the wrapped Element Android version of the app distributed out to Josh, Loisel and Anton, we had collected some solid sample conversations that have helped to inform which features are essential to the quiri experience and which new features we might like to build on top of it. This has given me more confidence in building from scratch using Flutter.

There is an actively maintained matrix SDK for flutter as well as the associated open-source app that depends on it (which has decent reviews in the Play Store). So my thinking is that it's probably a similar effort for me to get up speed on android vs build from scratch in Flutter (maybe I will just build on top of fluffychat...) but if I go with Flutter at least I will understand how all of the code works (because I will write it). Whereas the experience with android right now is one of generally being frustrated and lost in a large and complex codebase.

While I feel that there is some risk that Flutter will end up being the wrong tool due to some shortcomings, it appears to be a pretty solid tool and Google is quite active in improving it. If it plays out well, it will be easy enough to have desktop and web clients as well as android and iOS.

Getting Started

I had just bought a new macbook so I have had a pretty fresh start. Here are the things that I already installed:

  • iTerm2
  • ohmyzsh
  • xcode and the xcode command line tools (big download)
  • vs code
  • android studio (to maybe write flutter in)
  • docker desktop

For flutter I just jumped to their install homepage and went from there.

flutter doctor raised a couple of issues

  • Unable to locate Android SDK.
    • I needed to open Android Studio, which had a first time run wizard that installed the android SDK for me
  • CocoaPods not installed.
  • cmdline-tools component is missing
    • on the Android Studio startup modal, click More actions > SDK Manager > SDK Tools tab > check Android SDK command line tools (latest) and then Apply or OK
  • Android license status unknown.
    • flutter doctor --android-licenses

I am going to start with just android devices so I need to take the Android Setup steps as well.

IDE

I chose android studio because I imagine google has put effort into making it the ideal development experience. I could definitely see myself switching to VS code though.

I got as far as running the sample app on my device.

Next steps

I think that I will follow the more detailed intro to creating an app that follows this setup. And then probably go back to the Flutter course I started on Udemy a while back.

I will need to get some initial designs going with Loisel as well to help guide my efforts.

· 7 min read
Nigel Maynard

To fork or not to fork?

All of the workflows were brought over with the code. They start failing and sending emails. They should be disabled until you figure out what they do/how to fix them.

Running the project for the first time after forking

https://github.com/gradle/gradle/issues/9361 I blindly selected the remove option. I should probably come back and understand this better in the future

Then run on Pixel 4a emulator And it just runs! Now to change the package name so that we can release to play store under a name that we own

Trying to rename the packages to io.quiri.app Followed this post Built but with some warnings that may have not been there previously…

Back to librarifying vector

Starting steps

Read up on product flavours in libraries later Can’t have google-services.json in library Now getting error: incompatible types: <null> cannot be converted to int @com.airbnb.epoxy.EpoxyModelClass(layout = null) Maybe also doesn’t work well in library modules… Github issue The jsonviewer library project uses the EpoxyModelClass and it uses the R2.layout strategy with no complaints… Going to try this Got issues with APPLICATION_ID, so I swapped it for LIBRARY_PACKAGE_NAME Using FCM in a library module I just commented out apply plugin: com.google.gms.google-services in the gplay flavour Const val initializer should be a constant value

Creating the Quiri app module

Loosely followed this File > New > Module Name it io.quiri.app minSDK is 21 (see dependencies.gradle) Should change the quiri manifest to read minSdk from the same file Choose blank activity When gradle tries to resolve, it has a problem with

Just remove those two lines. The IDE added them for some reason, don’t know why it broke gradle build s fine as long as vector is not a dependency of quiri Add vector as a dependency of quiri

Had to create the flavours in quiri as well

Manifest Merging

The quiri main./AndroidManifest.xml had these problems (merged manifest)

Clicking the suggested fixes removed the errors… And then it builds and installs to emulated device!

Hilt DI Errors

BUT there is some issues with Hilt being used in a “feature module”

    Process: io.quiri.app, PID: 4409
java.lang.RuntimeException: Unable to instantiate application im.vector.app.VectorApplication: java.lang.ClassNotFoundException: Didn't find class "im.vector.app.VectorApplication" on path: DexPathList[[zip file "/data/app/~~HJX48J0ULpskvHJ8JQO44Q==/io.quiri.app-tcmSBf87Qu2FfwNszXOYZw==/base.apk"],nativeLibraryDirectories=[/data/app/~~HJX48J0ULpskvHJ8JQO44Q==/io.quiri.app-tcmSBf87Qu2FfwNszXOYZw==/lib/x86, /data/app/~~HJX48J0ULpskvHJ8JQO44Q==/io.quiri.app-tcmSBf87Qu2FfwNszXOYZw==/base.apk!/lib/x86, /system/lib, /system_ext/lib]]
at android.app.LoadedApk.makeApplication(LoadedApk.java:1244)
at android.app.ActivityThread.handleBindApplication(ActivityThread.java:6683)
at android.app.ActivityThread.access$1300(ActivityThread.java:237)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1913)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:223)
at android.app.ActivityThread.main(ActivityThread.java:7656)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
Caused by: java.lang.ClassNotFoundException: Didn't find class "im.vector.app.VectorApplication" on path: DexPathList[[zip file "/data/app/~~HJX48J0ULpskvHJ8JQO44Q==/io.quiri.app-tcmSBf87Qu2FfwNszXOYZw==/base.apk"],nativeLibraryDirectories=[/data/app/~~HJX48J0ULpskvHJ8JQO44Q==/io.quiri.app-tcmSBf87Qu2FfwNszXOYZw==/lib/x86, /data/app/~~HJX48J0ULpskvHJ8JQO44Q==/io.quiri.app-tcmSBf87Qu2FfwNszXOYZw==/base.apk!/lib/x86, /system/lib, /system_ext/lib]]
at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:207)
at java.lang.ClassLoader.loadClass(ClassLoader.java:379)
at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
at android.app.AppComponentFactory.instantiateApplication(AppComponentFactory.java:76)
at androidx.core.app.CoreComponentFactory.instantiateApplication(CoreComponentFactory.java:52)
at android.app.Instrumentation.newApplication(Instrumentation.java:1158)
at android.app.LoadedApk.makeApplication(LoadedApk.java:1236)
at android.app.ActivityThread.handleBindApplication(ActivityThread.java:6683)
at android.app.ActivityThread.access$1300(ActivityThread.java:237)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1913)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:223)
at android.app.ActivityThread.main(ActivityThread.java:7656)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
Suppressed: java.lang.NoClassDefFoundError: Failed resolution of: Lim/vector/app/Hilt_VectorApplication;
at java.lang.VMClassLoader.findLoadedClass(Native Method)
at java.lang.ClassLoader.findLoadedClass(ClassLoader.java:738)
at java.lang.ClassLoader.loadClass(ClassLoader.java:363)
... 14 more
Caused by: java.lang.ClassNotFoundException: Didn't find class "im.vector.app.Hilt_VectorApplication" on path: DexPathList[[zip file "/data/app/~~HJX48J0ULpskvHJ8JQO44Q==/io.quiri.app-tcmSBf87Qu2FfwNszXOYZw==/base.apk"],nativeLibraryDirectories=[/data/app/~~HJX48J0ULpskvHJ8JQO44Q==/io.quiri.app-tcmSBf87Qu2FfwNszXOYZw==/lib/x86, /data/app/~~HJX48J0ULpskvHJ8JQO44Q==/io.quiri.app-tcmSBf87Qu2FfwNszXOYZw==/base.apk!/lib/x86, /system/lib, /system_ext/lib]]
at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:207)
at java.lang.ClassLoader.loadClass(ClassLoader.java:379)
at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
... 17 more

Looks like there is specific way to use hilt in a feature module Basic Hilt https://developer.android.com/training/dependency-injection/hilt-android Hilt with “feature modules” https://developer.android.com/training/dependency-injection/hilt-multi-module Tried just following the instructions for Basic Hilt setup in the quiri gradle file Had to do this for kotlin(“kapt”) Aaand got some fresh errors related to kapt

I just needed to add hilt dependencies into the quiri gradle in the same way that they were added to the vector gradle

Weird layout-inflater issue Before this problem I had a problem that seemed to do with the fact that quiri app had a layout xml with the same name as one in vector Just removed the layout xml that was created with the project and its associated activity https://stackoverflow.com/questions/58554751/java-lang-nullpointerexception-missing-required-view-with-id Then I get a very non-descript error about the layout inflater This guy describes the problem in more details https://stackoverflow.com/a/44143105 This guy talks about how to debug it https://stackoverflow.com/a/40112945

So I just add an “any java exception” breakpoint and then start adding conditions to skip common exceptions until I hit something interesting https://www.jetbrains.com/help/idea/using-breakpoints.html#breakpoint_condition My condition: !(this instanceof ErrnoException) && !(this instanceof UnixException) && !(this instanceof FileAlreadyExistsException) && !(this instanceof ClassNotFoundException) && !(this instanceof NoSuchFileException) && !(this instanceof FileNotFoundException) There was some stackoverflow that talked about appcompat being a part of the problem… I lost that one

Same for logintoucharea

SOLUTION I just had to remove the application tag from the quiri AndroidManifest.xml It is clear that I don’t understand what I am doing but at least it is building now. It will be interesting to see how I will actually start adding my own components while using their code…

First attempt notes

Getting Started

  • pulled the code
  • used "import existing code" to import the project
  • got a sync error
    • something about sha256 hash maybe causing errors
    • chose the "remove that setting" option
  • then build starts!
    • This version of the Android Support plugin for IntelliJ IDEA (or Android Studio) cannot open this project, please retry with version 4.2 or newer.
    • upgraded android studio
    • opted to give more memory to android studio heap when prompted
    • I don't recall if I installed JDK 1.8 previously but it seemed to be needed
    • had issues with running avd on my macbook. Running on device now instead

TODO

  • change some code and debug something
  • change app config to connect to local
    • org/matrix/android/sdk/api/Matrix.kt
      • the configuration gets injected in here?
      • I need to learn more about android...
    • this is the config class src/main/java/org/matrix/android/sdk/api/MatrixConfiguration.kt
      • does it fetch values from a config file somewhere? I want to be able to set it to my local machine for dev
        • and to the quiri server otherwise
    • I think this is the main place that it loads config from: src/main/java/im/vector/app/VectorApplication.kt
      • for now I can just hardcode the integrationUrl into VectorApplciation?
    • this is the thing that lods those URLs src/main/java/org/matrix/android/sdk/internal/session/integrationmanager/IntegrationManager.kt
    • search for homeserver in the code yadoi
  • how to connect to locally running matrix server from device