andregasser / bigbone

BigBone - A Mastodon Client Library for Java and Kotlin
https://bigbone.social
MIT License
62 stars 15 forks source link

Fail to import MastodonClient #280

Closed PaulTrembla closed 1 year ago

PaulTrembla commented 1 year ago

Hello everyone,

I work with Android Studio. I inserted maven {url "https://s01.oss.sonatype.org/content/repositories/snapshots/" } in settings.gradle/repositories, I see that the classes were added at the external libraries level, but the class MastodonClient (and others) is not recognized in my java code. And import social.bigbone.MastodonClient; gives an error. Of course I inserted implementation "social.bigbone:bigbone:2.0.0-SNAPSHOT" and implementation "social.bigbone:bigbone-rx:2.0.0-SNAPSHOT" in build.gradle (:app). Using dependencies is usually not a problem in my applications, but this time is a real headache. Do you have any clue that can solve this problem?

Thanks a lot for your help.

bocops commented 1 year ago

Hi Paul,

I'm working with Android Studio as well. My configuration is as follows.

In settings.gradle:

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        // ...
        maven {
            url "https://s01.oss.sonatype.org/content/repositories/snapshots/"
        }
    }
}

In build.gradle (:app):

dependencies {
    // ...
    implementation 'social.bigbone:bigbone:2.0.0-SNAPSHOT'
}

With this, I can access MastodonClient in both Java and Kotlin code as expected.

PaulTrembla commented 1 year ago

Hi bocops,

Thanks for answering. I did the same thing as you. "Sync Project with Gradle Files" works fine. I also see this library in the "External Libraries" section. But impossible to declare a mastodonClient in my MainActivity class. The class name remains in red. Also impossible to insert the import social.bigbone.MastodonClient statement. MastodonClient could not be found.

I've been working with Android Studio for several years, and this is the first time I've had so much difficulty using an imported class. I program in Java. Is there a problem with the fact that the classes in this library seem to be in Kotlin?

Thank you very much for trying to help me.

andregasser commented 1 year ago

Hello Paul,

I am not really familiar with Android Studio, but what happens when you run a ./gradlew build --refresh-dependencies from your terminal in Android Studio? Does this help?

bocops commented 1 year ago

Is there a problem with the fact that the classes in this library seem to be in Kotlin?

My project using the library is in Kotlin, but I tested this by creating a Java class that imports and uses MastodonClient just fine.

If anything, I found Android Studio to be somewhat stubborn when dealing with SNAPSHOT dependencies like this one currently is. Refreshing your dependencies as @andregasser suggested would be my next step as well.

PaulTrembla commented 1 year ago

Hi André and bocops,

the 'gradlew build --refresh-dependencies' command worked fine: BUILD SUCCESSFUL in 1m 45s 82 actionable tasks: 53 executed, 29 up-to-date

But the MastodonClient class is still in red, like all the other classes (Status, Range) used in the Get Home Timeline example described in the USAGE.md file.

I am a computer science teacher. I teach the development of Android applications with Android Studio, in which I integrate sending tweets and reading the Timeline of an authenticated user, with Twitter. I have been using Twitter for several years but with Mr. Musk's new rules, I can no longer, and I no longer want to, use it. I was introduced to Mastodon and I am very interested in integrating it into my course.

I'm trying as best I can to resolve this problem, but I don't see any logic in Android Studio not recognizing these classes. For example: error: cannot find symbol MastodonClient client;

Additionally, I activated auto import.

If you have other suggestions, I will gladly take them.

A huge thank you.

andregasser commented 1 year ago

Hello Paul,

Sorry to hear that you're still having issues. Do you mind sharing a small Android Studio reproducer project, so that we can have a look at it? In parallel, I will have a look in Android Studio as well.

Thanks!

PaulTrembla commented 1 year ago

Hi André,

Here's a little project made with Android Studio Dolphin 2021.3.1. MastodonTest.zip

Thank you very very much.

bocops commented 1 year ago

Hey Paul,

this is really weird. I'm seeing exactly what you describe after opening your example project. All BigBone classes show up with a "Cannot resolve symbol" in your MainActivity.java, with no way to import a class via Alt+Enter.

However, if I create a new class in your project and try to use MastodonClient there, everythings works as expected. In fact, if I just hit enter anywhere in your MainActivity.java, auto-import suggestions start popping up there as well. I have no idea what might cause this, but it seems to be an Android Studio problem rather than anything else(?).

PattaFeuFeu commented 1 year ago

Here's a little project made with Android Studio Dolphin 2021.3.1.

@PaulTrembla With Android Studio Giraffe 🦒 on macOS, I’m not facing issues with your project. 🤔 I checked out the project, opened MainActivity.java in your project, and could import all BigBone classes with ⌥+Enter

PaulTrembla commented 1 year ago

Good evening to all of you,

Something new on my side.

Following PattaFeuFeu's message, I installed Android Studio Giraffe and all the classes used from the BigBone library are now recognized. The imports are automatically inserted. Everything compiles perfectly. The project has not changed, which is the one I shared.

You were right bocops, the problem seems to come from Android Studio.

I am now working on another problem. Executing the statement 'client = new MastodonClient.Builder(instanceHostname).accessToken(accessToken).build();' generates the following Exception:

'java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.mastodontest/com.example.mastodontest.MainActivity}: social.bigbone.api.exception.BigBoneRequestException: Unable to fetch instance version' .

The access token is the one I received when configuring my application on Mastodon, and my mastodon instance is botsin.space (the one used for my first contact with Mastodon).

As I'm new to Mastodon, I must surely be making a beginner's mistake. If you have a clue, I'll gladly take it.

Thank you to all of you.

andregasser commented 1 year ago

Hello all,

As a first step, I have collected the nodeinfo information from botsin.space and the one from mastodon.social as a reference for comparison. When the BigBone client instance is built, we try to retrieve the instance version via nodeinfo first. If that fails, we fall back to a simple REST call to GET https://botsin.space/api/v2/instance and if that fails too, we fall back to GET https://botsin.space/api/v1/instance.

First, I have collected the published nodeinfo information from botsin.space. Here are the results:

Result from curl https://botsin.space/.well-known/nodeinfo:

{"links":[{"rel":"http://nodeinfo.diaspora.software/ns/schema/2.0","href":"https://botsin.space/nodeinfo/2.0"}]}

Result from curl https://botsin.space/nodeinfo/2.0:

{"version":"2.0","software":{"name":"mastodon","version":"4.1.9"},"protocols":["activitypub"],"services":{"outbound":[],"inbound":[]},"usage":{"users":{"total":8422,"activeMonth":2505,"activeHalfyear":3589},"localPosts":23654836},"openRegistrations":true,"metadata":{}}

I have also collected the same info for another instance, mastodon.social, as a reference / for comparison:

Result from curl https://mastodon.social/.well-known/nodeinfo:

{"links":[{"rel":"http://nodeinfo.diaspora.software/ns/schema/2.0","href":"https://mastodon.social/nodeinfo/2.0"}]}

Result from curl https://mastodon.social/nodeinfo/2.0:

{"version":"2.0","software":{"name":"mastodon","version":"4.2.1"},"protocols":["activitypub"],"services":{"outbound":[],"inbound":[]},"usage":{"users":{"total":1631518,"activeMonth":288540,"activeHalfyear":835788},"localPosts":70715030},"openRegistrations":true,"metadata":{}}

The error message that @PaulTrembla mentions, happens in the fallback code, where we try to retrieve the instance version via REST calls.

Here's the partial results of the REST API calls of instance botsin.space:

Result of GET https://botsin.space/api/v2/instance:

{"domain":"botsin.space","title":"botsin.space","version":"4.1.9", ...

Result of GET https://botsin.space/api/v1/instance:

{"uri":"botsin.space","title":"botsin.space","short_description":"A Mastodon instance for bots and bot allies.","description":"This instance is for bots and bot allies. Anyone can sign up to be a user here or run your bot here. Users agree to the Code of Conduct listed on the \u003ca href=\"/about/more\"\u003einformation page\u003c/a\u003e. You should also review the \u003ca href=\"/terms\"\u003eterms of service\u003c/a\u003e.\r\n\r\n","email":"colin@muffinlabs.com","version":"4.1.9", ...

Without digging deeper atm, I'd say that the version info published by the instance is correct. IMHO, the BigBone client builder should have parsed the nodeinfo correctly and not throw such an exception described by Paul at all (as the nodeinfo data is provided by the instance).

I just want to explicitly highlight here, that we need a working internet connection while we build the client instance, as we need to retrieve the instance version info.

I will have a closer look at this this evening if the issue should still be open by then.

Regarding the Android Studio version: I will add a note to the troubleshooting section in the README.md.

andregasser commented 1 year ago

Hello Paul,

Do you face the same issue when you run the following sample from our sample-java Gradle module?

sample-java/src/main/java/social/bigbone/sample/GetInstanceInfo.java

I ran it against botsin.space and did not get an expection but a proper Instance object printed on the terminal:

{
  "uri":"botsin.space",
  "title":"botsin.space",
  "short_description":"A Mastodon instance for bots and bot allies.",
  "description":"This instance is for bots and bot allies. Anyone can sign up to be a user here or run your bot here. Users agree to the Code of Conduct listed on the \u003ca href\u003d\"/about/more\"\u003einformation page\u003c/a\u003e. You should also review the \u003ca href\u003d\"/terms\"\u003eterms of service\u003c/a\u003e.\r\n\r\n",
  "email":"colin@muffinlabs.com",
  "version":"4.1.9",
  ...
}
bocops commented 1 year ago

I just tested by adapting one of our samples like so:

package social.bigbone.sample;

import social.bigbone.MastodonClient;
import social.bigbone.api.exception.BigBoneRequestException;

public class GetServerVersion {
    public static void main(final String[] args) throws BigBoneRequestException {
        final String instance = args[0];

        // Instantiate client
        final MastodonClient client = new MastodonClient.Builder(instance)
                .build();

        System.out.println(client.getInstanceVersion());
    }
}

This does return valid version information for botsin.space as well as for multiple other instances I tested. However, after shutting down internet connection, I get exactly the error pointed out by Paul, so @andregasser your suspicion about connectivity was right. :)

The stack trace points to the last line of MastodonClient.getInstanceVersion(), where the exception is thrown. I'm not sure if we should (or even can) check for connectivity ourselves, because that depends on the platform that the code happens to run on.

andregasser commented 1 year ago

Thanks @bocops for reproducing this. 👍

The stack trace points to the last line of MastodonClient.getInstanceVersion(), where the exception is thrown. I'm not sure if we should (or even can) check for connectivity ourselves, because that depends on the platform that the code happens to run on.

Hm, honestly, I think checking for Internet connectivity is not the job of our library, but more the responsibility of the enclosing application. Just imaging what would happen if an app uses 10-15 libraries, each checking for Internet connectivity on its own.

What about this pragmatic "fix": We change the exception message for this case to something like this: Unable to fetch instance version. Please check internet connectivity.

@bocops As you are also working on an Android app (Altamira), what do you do in this case?

@PaulTrembla Can you confirm your code works, once Internet connectivity is available for your app?

bocops commented 1 year ago

As you are also working on an Android app (Altamira), what do you do in this case?

In my app, most of the work happens in background tasks that are simply restarted at a later time if they fail for any reason. For unrelated reasons, they are typically only started if the deviced detects an unmetered connection, which of course rules out most of those connectivity failure cases in the first place.

On top of that, all MastodonClient functionality is wrapped in try/catch blocks to either fall back to previously cached data where possible, or alert the user if necessary.

PaulTrembla commented 1 year ago

Hello all,

I tried getInstanceInfo.java as asked by André. The same Exception is always when instantiating the client.

I always work with a virtual device for my tests. I never have a problem with it, and its Internet access is ok.

I'm going to do some more testing later because I have to go to work. I live in Quebec, Canada where it is currently 7:30 am.

Thank you very much for your help.

andregasser commented 1 year ago

Hi Paul,

Could you share a full stack trace of your app when the exception is thrown?

Are other apps that require Internet connectivity running fine in your emulator on that same machine?

Thx for checking!

PattaFeuFeu commented 1 year ago

I was just able to reproduce the described issue with just the code in the MainActivity and additional try-catch to get around Java’s complaining.

I ran it on an emulated API level 30 device:

FATAL EXCEPTION: main
Process: com.example.mastodontest, PID: 23583
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.mastodontest/com.example.mastodontest.MainActivity}: social.bigbone.api.exception.BigBoneRequestException: Unable to fetch instance version
    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3449)
    at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3601)
    at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85)
    at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
    at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2066)
    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: social.bigbone.api.exception.BigBoneRequestException: Unable to fetch instance version
    at social.bigbone.MastodonClient$Builder.getInstanceVersion(MastodonClient.kt:634)
    at social.bigbone.MastodonClient$Builder.build(MastodonClient.kt:705)
    at com.example.mastodontest.MainActivity.onCreate(MainActivity.java:22)
    at android.app.Activity.performCreate(Activity.java:8000)
    at android.app.Activity.performCreate(Activity.java:7984)
    at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1309)
    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3422)
    at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3601)
    at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85)
    at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
    at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2066)
    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)

I’ll try to investigate what’s happening here now.

PattaFeuFeu commented 1 year ago

Found the issue: The sample project @PaulTrembla posted doesn’t take care of moving the network activity to the background thread so we get an android.os.NetworkOnMainThreadException.

BigBone unfortunately currently is very bad when it comes to exception handling: The chain is broken in many places and we do not propagate a caught exception so debugging was necessary to find out the root cause.

I will create an issue to ensure that we add caught exceptions to the new BigBone*Exceptions we throw. ➡️ #282

For your problem @PaulTrembla:

You need to run the BigBone calls on a background thread. This can be done with RxJava (see bigbone-rx), in Kotlin with Coroutines, or basic Thread handling.

Here is a very simple example:

package com.example.mastodontest;

import android.os.Bundle;
import android.util.Log;

import androidx.appcompat.app.AppCompatActivity;

import social.bigbone.MastodonClient;
import social.bigbone.api.Pageable;
import social.bigbone.api.Range;
import social.bigbone.api.entity.Status;
import social.bigbone.api.exception.BigBoneRequestException;

public class MainActivity extends AppCompatActivity {
    String accessToken = "***myToken***";
    String instanceHostname = "botsin.space";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        new Thread(() -> {
            try {
                MastodonClient client = new MastodonClient.Builder(instanceHostname).accessToken(accessToken).build();
                Pageable<Status> timeline = client.timelines().getHomeTimeline(new Range(null, null, 5)).execute();
                timeline.getPart().forEach(status -> {
                    System.out.println(status.getContent());
                });
            } catch (BigBoneRequestException e) {
                Log.d(MainActivity.class.getSimpleName(), "Could not get home timeline", e);
            }
        }).start();

    }
}
PaulTrembla commented 1 year ago

Hello everyone,

PattaFeuFeu is right. Since Android 3.0/Honeycomb/API 11, potentially long-running tasks, such as network access, must be handled in a separate thread (background thread).

This is what I teach my students, and we use it in the application that we build together. I didn't think about this possibility this time, because I didn't see the 'android.os.NetworkOnMainThreadException' at runtime.

I ran the application created with my students which allows, among other things, a user to authenticate with Twitter, and everything works as expected. To answer André's request, another application requiring Internet connectivity is indeed running normally in my emulator on the same machine.

I copied the entire PattaFeuFeu code, inserting my access token. Running the code in a Thread significantly reduced the stack trace. Included is the full stack trace generated during the last execution of the application.

But the problem still seems to be focused on: 'MastodonClient client = new MastodonClient.Builder(instanceHostname).accessToken(accessToken).build();' (line 25).

This project was also tested on a computer at work, with a colleague. Same Android Studio (Giraffe), same device with API 33, same result.

For information : Android Gradle Plugin Version: 7.4.2 Gradle Version: 7.5

Thanks again for helping me.

FullStackTrace

PattaFeuFeu commented 1 year ago

@PaulTrembla For future issues, please try to paste code or stacktraces as text, not as screenshots as it makes reading, copying, and searching among other tasks way harder if it’s a photo. 😊

Learning to use the debugger and breakpoints is probably a good task to teach your students how to find issues in code written in languages that support a debugger. I placed a breakpoint in line 654 of MastodonClient, so right here in the catch block:

https://github.com/andregasser/bigbone/blob/2e8392944f238a407286873c69bacf40fcc8c59a/bigbone/src/main/kotlin/social/bigbone/MastodonClient.kt#L653-L655

This yielded a security exception java.lang.SecurityException: Permission denied (missing INTERNET permission?) as you didn’t specify the INTERNET permission in AndroidManifest.xml.

Please add it to the Manifest so that the start of the file looks as follows:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <uses-permission android:name="android.permission.INTERNET" />

    <application
     ...

I don’t have an access token on hand right now so I cannot test it all the way, but that gets me over the initial hurdle to even setup the MastodonClient.


Edit: Acquired an access token now and was able to test: It works. 😊 I changed the log output to timeline.getPart().forEach(status -> System.out.println(status.getCreatedAt())); to have less output and I got the following in the logs:

23:43:17.717 System.out                           I  2023-10-13T21:33:37.000Z
23:43:17.717 System.out                           I  2023-10-13T21:28:48.000Z
23:43:17.717 System.out                           I  2023-10-13T21:25:56.000Z
23:43:17.717 System.out                           I  2023-10-13T21:07:56.000Z
23:43:17.717 System.out                           I  2023-10-13T21:04:48.000Z

so it’s working.

If you want to change some view in the UI, remember to switch back to the UI thread for that. With my simple example from above it will become really cumbersome really quickly, so I’d really recommend to look into Coroutines (and Kotlin instead of Java in general!) or at least RxJava.

PaulTrembla commented 1 year ago

In the application that I built with my students, it was not necessary to specify INTERNET permission because this permission was already specified in a library that I was using.

Again, it didn't occur to me to check this. Even if this permission is of the normal type (not dangerous), it must obviously still be specified.

Everything works perfectly now. I feel like it will be a pleasure to work with Mastodon and BigBone.

Thank you very much for your help, and especially for the time you spent.

You are experienced people and I have learned a lot from you.

Thank you also for your many advice. They are much appreciated.

Be well and take care of yourself.

andregasser commented 1 year ago

Hello everyone involved,

Excellent troubleshooting job. Thanks to you all. I just want to quickly mention to @PaulTrembla , that I usually post news on Mastodon under the BigBone hashtag. so if you want to stay up-to-date on this project, I'd suggest that you follow this one. Also feel free to connect with us on Mastodon. We are all there:

@andregasser@fosstodon.org @bocops@fosstodon.org @pattafeufeu@chaos.social

Feel free to reach out to us again, when you run into trouble. The library is still under active development, so hickups may occur (especially now, as we're still working on a snapshot). Of course, we also like positive feedback in general :-) Have a great day!