0

I'm having trouble sending objects in intents. I read that gsoup is one method to accomplish this task but I haven't been able to make it work.

Here's an example. This code tries to grab the title of this stackoverflow question. It makes use of jsoup, gson, IntentService and LocalBroadcastManager. It doesn't work because it causes an error:java.lang.StackOverflowError

MainActivity:

package com.example.elk.gsonbug;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.widget.Toast;
import com.google.gson.Gson;
import org.jsoup.nodes.Document;

public class MainActivity extends AppCompatActivity {

    private BroadcastReceiver broadcastReceiver;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        broadcastReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                if (intent.getAction().equals(JsonIntentService.ACTION_GET_QUESTION_TITLE)) {
                    Document documentQuestionTitle = new Gson().fromJson(intent.getStringExtra(JsonIntentService.EXTRA_QUESTION_TITLE_DOCUMENT), Document.class);
                    String question = documentQuestionTitle.selectFirst("div#question-header > h1").text();
                    Toast.makeText(context, question, Toast.LENGTH_LONG).show();
                }
            }
        };
        LocalBroadcastManager.getInstance(this).registerReceiver(broadcastReceiver, new IntentFilter(JsonIntentService.ACTION_GET_QUESTION_TITLE));


        JsonIntentService.startActionGetQuestionTitle(this, "https://stackoverflow.com/questions/53013644/how-to-send-jsoup-document-in-an-intent");
    }
}

JsonIntentService:

package com.example.elk.gsonbug;

import android.app.IntentService;
import android.content.Intent;
import android.content.Context;
import android.support.v4.content.LocalBroadcastManager;

import com.google.gson.Gson;

import org.jsoup.Connection;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;

import java.io.IOException;

public class JsonIntentService extends IntentService {
    static final String ACTION_GET_QUESTION_TITLE = "com.example.elk.gsonbug.action.GET_QUESTION_TITLE";
    private static final String EXTRA_URL = "com.example.elk.gsonbug.extra.URL";
    static final String EXTRA_QUESTION_TITLE_DOCUMENT = "com.example.elk.gsonbug.extra.QUESTION_TITLE_DOCUMENT";

    public JsonIntentService() {
        super("JsonIntentService");
    }

    public static void startActionGetQuestionTitle(Context context, String url) {
        Intent intent = new Intent(context, JsonIntentService.class);
        intent.setAction(ACTION_GET_QUESTION_TITLE);
        intent.putExtra(EXTRA_URL, url);
        context.startService(intent);
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        if (intent != null) {
            final String action = intent.getAction();
            if (action.equals(ACTION_GET_QUESTION_TITLE)) {
                final String url = intent.getStringExtra(EXTRA_URL);
                handleActionGetQuestionTitle(url);
            }
        }
    }

    private void handleActionGetQuestionTitle(String url) {
        Document documentQuestionTitle = null;
        try {
            Connection.Response responseDocumentQuestionTitle = Jsoup.connect(url)
                    .method(Connection.Method.GET)
                    .execute();
            documentQuestionTitle = responseDocumentQuestionTitle.parse();

            Intent intent = new Intent();
            intent.setAction(ACTION_GET_QUESTION_TITLE);
            intent.putExtra(EXTRA_QUESTION_TITLE_DOCUMENT, new Gson().toJson(documentQuestionTitle, Document.class));
            LocalBroadcastManager.getInstance(this).sendBroadcast(intent);

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

EDIT #1

Here's a simpler example (which doesn't manifest the problem...) MainActivity calls SecondActivity which created a Person object and sends it back with gson. It works without problem: D/MainActivity: Person{name='Bob', age=33} Does this mean that something in the Document class is not compatible with gson?

MainActivity:

package com.example.elk.gsonbug2;

import android.app.Activity;
import android.content.Intent;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;

import com.google.gson.Gson;


public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";

    private static final int REQUEST_PERSON = 0;

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

        Intent intent = new Intent(this, SecondActivity.class);
        startActivityForResult(intent, REQUEST_PERSON);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        if (requestCode == REQUEST_PERSON) {
            if (resultCode == Activity.RESULT_OK) {
                Person person = new Gson().fromJson(data.getStringExtra(SecondActivity.EXTRA_PERSON), Person.class);
                Log.d(TAG, person.toString());
            }
        }
    }
}

SecondActivity:

package com.example.elk.gsonbug2;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;

import com.google.gson.Gson;

public class SecondActivity extends AppCompatActivity {

    static final String EXTRA_PERSON = "com.example.elk.gsonbug2.extra.PERSON";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        Intent intent = new Intent();
        Person person = new Person("Bob", 33);
        intent.putExtra(EXTRA_PERSON, new Gson().toJson(person));
        setResult(Activity.RESULT_OK, intent);
        finish();
    }

}

2 Answers2

0

Make the request in MainActivity, that way you don't need to worry about sending objects back and forth.

xpland
  • 3
  • 2
0

What's the purpose for sending the whole object in intent? If you need to send any big and non-primitive object from one activity to another - store the object into SQLite database, ORM or internal/external storage and send only its id with intent.

  • Irregular Expression I just wanted the JsonIntentService class to make a request and get a `Document` out of it. After obtaining the document it should just send it back to the calling activity which then makes whatever it wants with it. I think it's a good design but if there's a problem with this let me know. Storing the object in SQLite database seems more work than is needed (don't even know how to do it to be honest). What I'm trying to do is pretty straight forward I just can't figure out what's causing the problem. –  Oct 26 '18 at 19:40
  • The problem is that the intent sending data has strict limits, it may be primitive type or String, or array of primitives. There's no way to send any other object this way. So jsoup Document can't be sent by attaching to intent. – Irregular Expression Oct 26 '18 at 23:08
  • If you have a service and want to get data (i.e. Jsoup Document) from it in your Activity, there're two ways to solve the problem: 1) data storage (ORM or other), 2) reactive solutions like JavaRx or Google LiveData, where your service asynchronously produce on background thread some data which activity subscribes on main thread. – Irregular Expression Oct 26 '18 at 23:18
  • In your case Rx or LiveData will be the best solution. In JavaRx you may solve such problem with one or two methods. – Irregular Expression Oct 26 '18 at 23:40
  • Irregular Expression "it may be primitive type or String, or array of primitives. There's no way to send any other object this way." That's not true, I created a Person object and was able to send it in an Intent with `gson` (see the edit I made). Anyways I will have a look at your suggestions –  Oct 27 '18 at 08:26
  • Sorry, but you're mistaken. Converting your object to Gson passes a String value extra to your Intent. You can't do this with a big and complex object. Yes, there're some types like Bundle or Parcelable which can be sent with intent, maybe you can implement Parcelable wrapper. See this question: https://stackoverflow.com/questions/2139134/how-to-send-an-object-from-one-android-activity-to-another-using-intents is it what you are looking for? But as for me, Rx or LiveData is easier for such cases. – Irregular Expression Oct 27 '18 at 15:15
  • Irregular Expression I read about Parcelable too but isn't that only applicable for classes that we write? Like if I write a Person class I would be able to make it parcelable. Do you know if I can I take a class such as `Document` from `Jsoup` and make it parcelable? –  Oct 27 '18 at 16:15
  • Parcelable is an interface, and Document isn't final and has a public constructor. So why not to extend your class from Document and implement Parcelable? I see no problem in creating ParcelableDocument class, if you want it. – Irregular Expression Oct 27 '18 at 19:35