0

I am completely new to Android Studio and just learned Object-oriented programming. My project requires me to build something on open-source code. I added a new menu item to a menu and want to start another activity once the user clicks the menu item with id: plot. I followed a recipe on the internet but got an error with it. It said 'method call expected' which means Terminal Fragment may be a method. I got confused, as I thought it is a class(an activity). Could anyone give me instructions to start an activity (turn to the next page with a different activity) in this case? What will be the simplest way to do this? Much Appreciated.

I added this in onOptionsItemSelected(MenuItem item).

if (id == R.id.plot){
            Intent intent = new Intent(TerminalFragment.this, MainActivity2.class);
            startActivity(intent);
        }

TerminalFragment.java (entire code)

package de.kai_morich.simple_bluetooth_le_terminal;

import android.app.Activity;
import android.app.AlertDialog;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.text.Editable;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.method.ScrollingMovementMethod;
import android.text.style.ForegroundColorSpan;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;

public class TerminalFragment extends Fragment implements ServiceConnection, SerialListener {

    private MenuItem menuItem;
    private enum Connected { False, Pending, True }

    private String deviceAddress;
    private SerialService service;

    private TextView receiveText;
    private TextView sendText;
    private TextUtil.HexWatcher hexWatcher;

    private Connected connected = Connected.False;
    private boolean initialStart = true;
    private boolean hexEnabled = false;
    private boolean pendingNewline = false;
    private String newline = TextUtil.newline_crlf;

    /*
     * Lifecycle
     */
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setHasOptionsMenu(true);
        //Register with activity
        // You must inform the system that your app bar fragment is participating in the population of the options menu.
        // tells the system that your fragment would like to receive menu-related callbacks.
        setRetainInstance(true);
        deviceAddress = getArguments().getString("device");
    }

    @Override
    public void onDestroy() {
        if (connected != Connected.False)
            disconnect();
        getActivity().stopService(new Intent(getActivity(), SerialService.class));
        super.onDestroy();
    }

    @Override
    public void onStart() {
        super.onStart();
        if(service != null)
            service.attach(this);
        else
            getActivity().startService(new Intent(getActivity(), SerialService.class)); // prevents service destroy on unbind from recreated activity caused by orientation change
    }

    @Override
    public void onStop() {
        if(service != null && !getActivity().isChangingConfigurations())
            service.detach();
        super.onStop();
    }

    @SuppressWarnings("deprecation") // onAttach(context) was added with API 23. onAttach(activity) works for all API versions
    @Override
    public void onAttach(@NonNull Activity activity) {
        super.onAttach(activity);
        getActivity().bindService(new Intent(getActivity(), SerialService.class), this, Context.BIND_AUTO_CREATE);
    }

    @Override
    public void onDetach() {
        try { getActivity().unbindService(this); } catch(Exception ignored) {}
        super.onDetach();
    }

    @Override
    public void onResume() {
        super.onResume();
        if(initialStart && service != null) {
            initialStart = false;
            getActivity().runOnUiThread(this::connect);
        }
    }

    @Override
    public void onServiceConnected(ComponentName name, IBinder binder) {
        service = ((SerialService.SerialBinder) binder).getService();
        service.attach(this);
        if(initialStart && isResumed()) {
            initialStart = false;
            getActivity().runOnUiThread(this::connect);
        }
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        service = null;
    }

    /*
     * UI
     */
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_terminal, container, false);
        receiveText = view.findViewById(R.id.receive_text);                          // TextView performance decreases with number of spans
        receiveText.setTextColor(getResources().getColor(R.color.colorRecieveText)); // set as default color to reduce number of spans
        receiveText.setMovementMethod(ScrollingMovementMethod.getInstance());

        sendText = view.findViewById(R.id.send_text);
        hexWatcher = new TextUtil.HexWatcher(sendText);
        hexWatcher.enable(hexEnabled);
        sendText.addTextChangedListener(hexWatcher);
        sendText.setHint(hexEnabled ? "HEX mode" : "");

        View sendBtn = view.findViewById(R.id.send_btn);
        sendBtn.setOnClickListener(v -> send(sendText.getText().toString()));
        return view;
    }

    @Override
    public void onCreateOptionsMenu(@NonNull Menu menu, MenuInflater inflater) {
        inflater.inflate(R.menu.menu_terminal, menu);
        menu.findItem(R.id.hex).setChecked(hexEnabled);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int id = item.getItemId();
        if (id == R.id.clear) {
            receiveText.setText("");
            return true;
        } if (id == R.id.plot){
            Intent intent = new Intent(TerminalFragment.this, MainActivity2.class);
            startActivity(intent);
        }else if (id == R.id.newline) {
            String[] newlineNames = getResources().getStringArray(R.array.newline_names);
            String[] newlineValues = getResources().getStringArray(R.array.newline_values);
            int pos = java.util.Arrays.asList(newlineValues).indexOf(newline);
            AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
            builder.setTitle("Newline");
            builder.setSingleChoiceItems(newlineNames, pos, (dialog, item1) -> {
                newline = newlineValues[item1];
                dialog.dismiss();
            });
            builder.create().show();
            return true;
        } else if (id == R.id.hex) {
            hexEnabled = !hexEnabled;
            sendText.setText("");
            hexWatcher.enable(hexEnabled);
            sendText.setHint(hexEnabled ? "HEX mode" : "");
            item.setChecked(hexEnabled);
            return true;
        } else {
            return super.onOptionsItemSelected(item);
        }
    }

    /*
     * Serial + UI
     */
    private void connect() {
        try {
            BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
            BluetoothDevice device = bluetoothAdapter.getRemoteDevice(deviceAddress);
            status("connecting...");
            connected = Connected.Pending;
            SerialSocket socket = new SerialSocket(getActivity().getApplicationContext(), device);
            service.connect(socket);
        } catch (Exception e) {
            onSerialConnectError(e);
        }
    }

    private void disconnect() {
        connected = Connected.False;
        service.disconnect();
    }

    private void send(String str) {
        if(connected != Connected.True) {
            Toast.makeText(getActivity(), "not connected", Toast.LENGTH_SHORT).show();
            return;
        }
        try {
            String msg;
            byte[] data;
            if(hexEnabled) {
                StringBuilder sb = new StringBuilder();
                TextUtil.toHexString(sb, TextUtil.fromHexString(str));
                TextUtil.toHexString(sb, newline.getBytes());
                msg = sb.toString();
                data = TextUtil.fromHexString(msg);
            } else {
                msg = str;
                data = (str + newline).getBytes();
            }
            SpannableStringBuilder spn = new SpannableStringBuilder(msg + '\n');
            spn.setSpan(new ForegroundColorSpan(getResources().getColor(R.color.colorSendText)), 0, spn.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
            receiveText.append(spn);
            service.write(data);
        } catch (Exception e) {
            onSerialIoError(e);
        }
    }

    private void receive(byte[] data) {
        if(hexEnabled) {
            receiveText.append("Hello" + TextUtil.toHexString(data) + '\n');
        } else {
            String msg = new String(data);
            if(newline.equals(TextUtil.newline_crlf) && msg.length() > 0) {
                // don't show CR as ^M if directly before LF
                msg = msg.replace(TextUtil.newline_crlf, TextUtil.newline_lf);
                // special handling if CR and LF come in separate fragments
                if (pendingNewline && msg.charAt(0) == '\n') {
                    Editable edt = receiveText.getEditableText();
                    if (edt != null && edt.length() > 1)
                        edt.replace(edt.length() - 2, edt.length(), "");
                }
                pendingNewline = msg.charAt(msg.length() - 1) == '\r';
            }
            receiveText.append(TextUtil.toCaretString(msg, newline.length() != 0)); //print out data
        }
    }

    private void status(String str) {
        SpannableStringBuilder spn = new SpannableStringBuilder(str + '\n');
        spn.setSpan(new ForegroundColorSpan(getResources().getColor(R.color.colorStatusText)), 0, spn.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        receiveText.append(spn);
    }

    /*
     * SerialListener
     */
    @Override
    public void onSerialConnect() {
        status("connected");
        connected = Connected.True;
    }

    @Override
    public void onSerialConnectError(Exception e) {
        status("connection failed: " + e.getMessage());
        disconnect();
    }

    @Override
    public void onSerialRead(byte[] data) {
        receive(data);
    }

    @Override
    public void onSerialIoError(Exception e) {
        status("connection lost: " + e.getMessage());
        disconnect();
    }

}

menu_terminal.xml

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id="@+id/plot"
        android:title="PLOTDATA"
        app:showAsAction="always" />
    <item
        android:id="@+id/clear"
        android:icon="@drawable/ic_delete_white_24dp"
        android:title="Clear"
        app:showAsAction="always" />
    <item
        android:id="@+id/newline"
        android:title="Newline"
        app:showAsAction="never" />
    <item
        android:id="@+id/hex"
        android:title="HEX Mode"
        android:checkable="true"
        app:showAsAction="never" />
</menu>
TSLee
  • 33
  • 6

1 Answers1

0

Here, TerminalFragment is a fragment, not an activity. And so, instead of using TerminalFragment.this in new Intent(), you should use getActivity().

So, the final code would look something like this:

Intent intent = new Intent(getActivity(), MainActivity2.class);
startActivity(intent);

You can also check this: Intent from Fragment to Activity

ganjaam
  • 1,030
  • 3
  • 17
  • 29
  • Thanks. How do I distinguish fragment and activity from a class file? – TSLee May 22 '22 at 06:49
  • @TSLee the fragment classes **extends** Fragment class, the Activity classes extends Activity/AppCompatActivity class. As you can see, `class TerminalFragment extends Fragment` is extending `Fragment`. so it's a fragment class. – ganjaam May 22 '22 at 07:45
  • Usually the name of fragment classes ends with the suffix 'Fragment' and that of activity classes ends with 'Activity'. It is almost mandatory to follow this naming convention. – ganjaam May 22 '22 at 07:47
  • another practice is to keep fragment and activity classes in seperate packages. that way, it becomes easier to browse fragment and activity classes. – ganjaam May 22 '22 at 07:49
  • I found out that a fragment can also extend ListFragnment. – TSLee May 22 '22 at 09:03
  • Suppose MyClass.java is a fragment, but it extends the class X.java. It will be seen that class X will either extend Fragment, or class X will extend another class (say Y) and that class (class Y) will extend Fragment. – ganjaam May 22 '22 at 09:42
  • That's why the naming convention is quite important. As long as we see any class name with suffix 'Fragment', we can assume that it extends the Fragment at some point in the hierarchy. Almost all the libraries and SDKs follow the naming convention. So as long as we follow the naming conventions in our code as well, we can say whether a class is Fragment or not at a glance. – ganjaam May 22 '22 at 09:45
  • As mentioned earlier, Activity classes extend Activity or [AppCompatActivity](https://developer.android.com/reference/androidx/appcompat/app/AppCompatActivity). I'd like to highlight that, AppCompatActivity extends the class **FragmentActivity**, which is actually an Activity, not Fragment. It is a base class for activities that want to use the support-based Fragments. You can view the hierarchies by placing the cursor on the class name in android studio or in developer.android.com's reference of classes. – ganjaam May 22 '22 at 09:51
  • NOTE: the activites should also be specified in the AndroidManifest.xml file under the `` tag. Fortunately, when we declare new activity, android studio automatically updates the manifest file for us. so, from the manifest file you can see list of all the classes that are actually activities. But Fragments aren't specified like that. – ganjaam May 22 '22 at 09:57
  • Thanks for providing such an informative introduction. I am trying to go back from MainActivity2 to fragment by a menu item. I found a way from stackoverflow but it needs me to find a fragment container. What is a fragment container? Do you get any idea? – TSLee May 22 '22 at 10:50
  • I found out that this open-source code doesn't define its own layout. I found the method from https://stackoverflow.com/questions/23927500/get-back-to-a-fragment-from-an-activity. I think I should use another way. In this case, defining a layout for an existed fragment is not easy. – TSLee May 22 '22 at 11:02
  • It's true that using finish() is the simplest way. – TSLee May 22 '22 at 11:10
  • @TSLee as the name suggests, FragmentContainer contain Fragment. Suppose you have fragment classes named A, B and C. in activity P, you can define a fragment container view named W in xml and load any of A, B or C in W. Suppose you load fragment A in W. in the onclick event of a menu item, you can replace A with B or C. – ganjaam May 23 '22 at 03:26
  • @TSLee while finish() may appear to be the simplest way, you should also learn other ways. because it might not work for many cases. for instance, if I go from activity A to B and use a flag that clears the stack of activities, finish() will kinda close the app instead. – ganjaam May 23 '22 at 04:37
  • I need your help with https://stackoverflow.com/questions/72373350/how-to-send-data-sent-by-ble-device-in-a-fragment-to-a-new-activity-for-display – TSLee May 25 '22 at 08:08
  • @TSLee, I'm getting page not found in this link – ganjaam May 25 '22 at 08:53
  • @ ganjaam Sorry, I have rephrased the question. https://stackoverflow.com/questions/72374549/how-to-send-data-sent-by-ble-device-in-a-fragment-to-a-new-activity-for-display – TSLee May 25 '22 at 09:01