1

I have an implemented method in my class which assign instance variable. I want to create a method which waits until interface method runned and then returns instance variable which is assigned in there. If I couldn't find any solution,I need to re-code my class to handle this problem. As an example :

public class MyClass {
    private String /*or AnyObject*/ string;
    @Override
    public void onData(String value) {
    this.string=value;
    }
    public void callOnData(/*some param*/){
    //does some work and calls onData();
    }
    public String whatIwantTo(){
    //if onData called 
     //return this.string;
    //else wait until it recevied.
    }
}

After that I can call whatIwantTo() method from myMain class.

If you wonder What I tried, It looks like:

public class MyClass {
private String /*or AnyObject*/ string;
private static final class Lock {}
private final Object lock = new Lock();
void lock() {
    synchronized (lock) {
        while (string == null) {
            try {
                lock.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public void onData(String value) {
    this.string = value;
    synchronized (lock) {
        lock.notify();
    }
}

public void callOnData(/*some param*/) {
    //does some work and calls onData();
}

public String whatIwantTo() {
    callOnData();//No need to check if it is null at this time.
    Thread thread = new Thread(new Runnable() {
        @Override
        public void run() {
            lock();
        }
    });
    thread.start();
    try { 
        /*If I use this it freezes and never notifyed from onData*/
        /*Because thread is locked so it doesn't receive any data*/
        /*If I don't use this it returns null*/
        /*And then calling getString() from myMain class returns right value.*/
        thread.join();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return this.string;
}
public String getString() {
    return this.string;
}

}

and in myMain class:

String returned=whatIwantTo();
System.out.print(""+returned)//returns null or never reached.

OK. After @JBNizet request, I copy all code what I used is and I use it in android:

package makgun.webview;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.util.Log;
import android.webkit.JavascriptInterface;
import android.webkit.WebSettings;
import android.webkit.WebView;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 *  Created by makgun on 19.03.2017.
 */
public class MyClass {
    private Context context;
    private String string;
    private WebView webView;
    List<Notify> lists;
    private static final class Lock { }
    private final Object lock = new Lock();
    private final CountDownLatch latch = new CountDownLatch(1);
    MyClass(Context context) {
        this.context = context;
    }
    public interface Notify {
        void onNotify(String result);
    }

    void onNotifyListener(Notify notify) {
        if (lists == null)
            lists = new ArrayList<>();
        lists.add(notify);
    }

    private void setNotify(String result) {
        if (lists != null)
            for (Notify notify : lists)
                notify.onNotify(result);
    }
    String xyz(){
        try {
            synchronized (lock) {
                while (string ==null) {
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    Log.d("makgun", "xyz_after_wait");
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        Log.d("makgun","xyz_return : "+ string);
        return string;
    }
    private void ny(){
        try {
            synchronized (lock) {
                Log.d("makgun", "ny()");
                lock.notify();
                lock.notifyAll();
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    @SuppressLint({"SetJavaScriptEnabled", "AddJavascriptInterface", "JavascriptInterface"})
    private void initJs(){
        webView = new WebView(context);
        WebSettings webSettings = webView.getSettings();
        webSettings.setJavaScriptEnabled(true);
        webView.addJavascriptInterface(this, "Android");
    }

    private void runJs(String html) {
        webView.loadDataWithBaseURL("", html, "text/html", "charset=UTF-8", null);
    }
    @JavascriptInterface
    public String onData(String value) {
        Log.d("makgun",value);
        setNotify(value);//For now I can read it via this custom interface
        string =value;
        Log.d("makgun","string Setted");
        latch.countDown();
        return value;
    }
    private String LoadData(String inFile) {
        String tContents = "";
        try {
            InputStream stream = context.getResources().getAssets().open(inFile);
            int size = stream.available();
            byte[] buffer = new byte[size];
            stream.read(buffer);
            stream.close();
            tContents = new String(buffer);
        } catch (IOException e) {
            // Handle exceptions here
        }
        return tContents;
    }
    String getHtml() {
        return LoadData("script.html");
    }
    public void initJS() throws ExecutionException, InterruptedException {
        Activity activity=((Activity)context);
        Callable<Void> callable = new Callable<Void>() {
            @Override
            public Void call() throws Exception {
                initJs();
                return null;
            }
        };
        FutureTask<Void> task = new FutureTask<>(callable);
        activity.runOnUiThread(task);
        task.get(); // Blocks
    }
    public void runJS(final String html) throws ExecutionException, InterruptedException {
        Activity activity=((Activity)context);
        Callable<Void> callable = new Callable<Void>() {
            @Override
            public Void call() throws Exception {
                runJs(html);
                return null;
            }
        };
        FutureTask<Void> task = new FutureTask<>(callable);
        activity.runOnUiThread(task);
        task.get(); // Blocks
    }
    String whatIwantTo(String html) throws ExecutionException, InterruptedException {
        Log.d("makgun","initJS_started");
        long startTime=System.currentTimeMillis();
        initJS();
        long temp=System.currentTimeMillis();
        Log.d("makgun","initJS_finished in ["+(temp-startTime)+" ms]");
        runJS(html);
        Log.d("makgun","runJS_finished in ["+(System.currentTimeMillis()-temp)+" ms]");
        /*After this step it will call onData() but latch.await() locks before interface reached.*/
        // latch.await();
        return string;
    }


}

This is html which will load to webView (named as script.html):

<!doctype html>
<html lang="en-US">
<head>
</head>
<body>
<script>
Android.onData('Hello World I am here!');
</script>
<!--
 Empty Body Just For Test
-->
</body>
</html>

And Finally what I used from MainActivity is:

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

    Button button = (Button) findViewById(R.id.button);
    final MyClass myClass=new MyClass(MainActivity.this);
    myClass.onNotifyListener(new MyClass.Notify() {
        @Override
        public void onNotify(String result) {
            Log.d("makgun","OnNotify result: ["+result+"]");
        }
    });
    button.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {

            String returned = null;
            try {
                returned=myClass.whatIwantTo(myClass.getHtml());
            } catch (ExecutionException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Log.d("makgun","returned ["+returned+"]");
       }
    });
}

And the catlog with latch.await disabled!:

03-19 05:05:27.238 10963-10963/? D/makgun: InitJS_started
03-19 05:05:27.308 10963-10963/? D/makgun: initJS_finished in [71 ms]
03-19 05:05:27.318 10963-10963/? D/makgun: initJS_finished in [10 ms]
03-19 05:05:27.318 10963-10963/? D/makgun: returned [null]
03-19 05:05:27.438 10963-11153/? D/makgun: Hello World I am here!
03-19 05:05:27.438 10963-11153/? D/makgun: OnNotify result: [Hello World I am here!]
03-19 05:05:27.438 10963-11153/? D/makgun: string Setted

And Finally the catlog with latch.await NOT disabled!:

Nothing getted and app is freezed.
makgun
  • 155
  • 2
  • 8
  • 4
    Java does not have global variables. – David Choweller Mar 18 '17 at 22:02
  • @DavidChoweller look at [this](http://stackoverflow.com/a/4646591/5074052). – makgun Mar 18 '17 at 22:04
  • 2
    @makgun `static` variables are not **global**, because java doesn't have a **global namespace**. It's a *semantic distinction*. It isn't clear why you think you have a "global variable". – Elliott Frisch Mar 18 '17 at 22:18
  • @ElliottFrisch Ok. I am wrong. But in this OP I don't ask about anything related type of variable. I will update my question. Thanks. – makgun Mar 18 '17 at 22:21
  • I don't understand. your whatIwantTo() method directly calls callOnData(), which itself calls onData(), which itself sets this.string. So why would you have to wait? After the call to callOnData(), this.string has been set. – JB Nizet Mar 18 '17 at 23:15
  • @JBNizet yes,you are right. But I don't call onData method myself. After all statement finished the method exit. But after it finishes, it will do some works and call `onData()`. Think like that `Thread start() method.`,it maybe takes nanosecond or maybe never finish but it jumps next statement and does work inbackground using another Thread. – makgun Mar 18 '17 at 23:25

2 Answers2

2

You can use a CountdownLatch to do that easily. wait() and notify() can be used to, but they're too low level, and hard to use correctly.

Here is a complete minimal example.

import java.util.concurrent.CountDownLatch;

public class MyClass {
    private String string;

    private final CountDownLatch latch = new CountDownLatch(1);

    public void onData(String value) {
        this.string = value;
        latch.countDown();
    }

    public void callOnData(/*some param*/) {
        new Thread(() -> {
            try {
                Thread.sleep(100L);
            }
            catch (InterruptedException e) {
            }
            onData("hello");
        }).start();
    }

    public String whatIwantTo() throws InterruptedException {
        callOnData();
        latch.await();
        return this.string;
    }

    public static void main(String[] args) throws InterruptedException {
        MyClass m = new MyClass();
        System.out.println(m.whatIwantTo());
    }
}
JB Nizet
  • 678,734
  • 91
  • 1,224
  • 1,255
  • thanks for your answer, but it acts as what I tried. Removed `latch.await()` and I get correct result if I use `getString() method`. But with `latch.await()` it never ends. – makgun Mar 19 '17 at 00:16
  • 1
    What do you mean. You mean that you took my code, executed it, and it never ended? Or you meant that you wrote something else, and it never ended. Because if you wrote somthing else, I can't tell you what is wrong without seeing it. Post a complete, minimal example reproducing the problem, just like I did. Something that I can copy and paste, and execute. – JB Nizet Mar 19 '17 at 00:18
  • Directly copy/paste works but you call it manually. In my code it reach `onData()` from Interface and this method also break the interface to work. – makgun Mar 19 '17 at 00:24
0

Thanks for your help. Especially to @JBNizet. (I couldn't vote up your answer but thanks.). After some research I found why it freezes. The problem is Android related not Java.Because Android seperates thread as ui and non-ui. So if I block it from ui thread,it freezes the interface thread as well. So I call method from inside of new Thread(Runnable) and now it works. This is how my code looks like:

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

    Button button = (Button) findViewById(R.id.button);
    final MyClass myClass=new MyClass(MainActivity.this);
    /*myClass.onNotifyListener(new MyClass.Notify() {
        @Override
        public void onNotify(String result) {
            Log.d("makgun","OnNotify result: ["+result+"]");
        }
    });*/ //No more needed!
    button.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {

            final  String[] returned=new String[1];
            try {
                Thread t=new Thread(new Runnable() {
                @Override
                public void run() {
                    long a=System.currentTimeMillis();
                    returned[0] =myClass.whatIwantTo(myClass.getHtml());
                    Log.d("makgun","Returned in ["+(System.currentTimeMillis()-a)+" ms]");
                    }
                });
                t.start();
                try {
                    t.join(/*timeout*/);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            } catch (ExecutionException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Log.d("makgun","returned ["+returned[0]+"]");
       }
    });
}

I also gain experience that if I do it from in doInBackground, the interface finished time is about [150 - 200 ms] but for above code, the time is about [1000 - 1500 ms] even if I load same data from my assets. This is huge differents. I couldn't figure out its why.

makgun
  • 155
  • 2
  • 8