Register a SA Forums Account here!
JOINING THE SA FORUMS WILL REMOVE THIS BIG AD, THE ANNOYING UNDERLINED ADS, AND STUPID INTERSTITIAL ADS!!!

You can: log in, read the tech support FAQ, or request your lost password. This dumb message (and those ads) will appear on every screen until you register! Get rid of this crap by registering your own SA Forums Account and joining roughly 150,000 Goons, for the one-time price of $9.95! We charge money because it costs us money per month for bills, and since we don't believe in showing ads to our users, we try to make the money back through forum registrations.
 
  • Post
  • Reply
FAT32 SHAMER
Aug 16, 2012



Volmarias posted:

I straight up told you to do this :negative:


You're welcome

oh you did and i forgot to click the link :doh:

i'm literally retarded

Adbot
ADBOT LOVES YOU

FAT32 SHAMER
Aug 16, 2012



I'm trying to verify that my app is receiving UDP traffic from port 12001 to rule out the app being broken and see if the device sending the UDP signals (a vehicle) is actually sending them. What's the best way to do this? there seems to be a lot of sniffers on the play store but they seem to require connection to a VPN and they seem to be dodgy.

I need a solution that doesnt require a rooted device as well

FAT32 SHAMER
Aug 16, 2012



Vesi posted:

these are two separate problems, you can test UDP receiving by sending data with for example netcat if you're in the same network
pre:
$ echo asd|nc -u <phone-ip> 12001
remember to ask the user to disable battery optimizations first:
https://developer.android.com/training/monitoring-device-state/doze-standby.html

also UDP seems like an awful idea in 2017, but maybe you don't have a choice?

it's for vehicle to vehicle communications so you can imagine what i'm dealing with :saddowns:

FAT32 SHAMER
Aug 16, 2012



I asked the big brains and the reason for using UDP is so vehicles can communicate their location quicker without worrying about a response packet and can get away with missing information.

FAT32 SHAMER
Aug 16, 2012



Ok boyos who wants so Stack Exchange e-points

https://stackoverflow.com/questions/45090837/nullpointerexception-on-asynctask-execute

dont let EE's write loving android apps

FAT32 SHAMER
Aug 16, 2012



baka kaba posted:

I can't see which is line 61 of MainActivity (where you're calling a method on a null object) but I'm guessing you're meant to be assigning to an mListeningService field in onCreate instead of creating a local variable?

Yeah I marked it here. The EE created the mAndroidUDP.startIt() method which looks like this

Java code:
    public void startIt() {

        listeningService.continueListening();

    }
and continueListening() simply has new startListening().execute() where startListening extends AsyncTask

Here's the onCreate method

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

        // init parameters
        started = false;
        counter = 0;

        IntentFilter filter =  new IntentFilter("ToActivity");

        mAndroidUDP = new AndroidUDP(this, this);
        mAndroidUDP.intent1 = new Intent(this, ListeningService.class);
        mAndroidUDP.intent2 = new Intent(this, PlaySound.class);
        startService(mAndroidUDP.intent1);
        startService(mAndroidUDP.intent2);

        mAndroidUDP.doStuff(this,filter);
        mAndroidUDP.startIt(); //this is the cause of the Null Object Reference


        mImageView = (ImageView) this.findViewById(R.id.image_view);

        mTextView = (TextView) findViewById(R.id.alert_text);
        mBlinkView = (TextView) findViewById(R.id.blinking_text);

        Animation mAnimation = new AlphaAnimation(0.0f, 1.0f);
        mAnimation.setDuration(100);
        mAnimation.setStartOffset(20);
        mAnimation.setRepeatCount(Animation.INFINITE);
        mAnimation.setRepeatMode(Animation.REVERSE);
        mBlinkView.startAnimation(mAnimation);
    }
If this wasnt due by monday of next week I would have just torn it all down and rebuilt it instead of trying to debug it and deal with this lovely code. I still have no idea why he decided to use enumerations and ordinal arrays over just using a dictionary/map and assigning the image and alert sounds as properties

FAT32 SHAMER
Aug 16, 2012



baka kaba posted:

Where's listeningService getting assigned in AndroidUDP?

Java code:
public class AndroidUDP extends Service
{



    enum alerts
    {
        ALERT_CLEAR, //0
        ALERT_FCW_IMMINENT, //1
        ALERT_DNPW_IMMINENT, //2
        ALERT_IMA_IMMINENT_LEFT, //3
        ALERT_IMA_IMMINENT_RIGHT, //4
        ALERT_CLW_IMMINENT, //5
        ALERT_LCW_IMMINENT_LEFT, //6
        ALERT_LCW_IMMINENT_RIGHT, //7
        ALERT_BSW_IMMINENT_LEFT, //8
        ALERT_BSW_IMMINENT_RIGHT, //9
        ALERT_FCW_ADVISORY, //10
        ALERT_DNPW_ADVISORY, //11
        ALERT_IMA_ADVISORY_LEFT, //12
        ALERT_IMA_ADVISORY_RIGHT, //13
        ALERT_BSW_ADVISORY_LEFT, //14
        ALERT_BSW_ADVISORY_RIGHT, //15
        ALERT_LTA_ADVISORY_LEFT, //16
        ALERT_LTA_ADVISORY_RIGHT, //17
        ALERT_LTA_ADVISORY_OPPOSITE //18
    }

    MainActivity mainActivity;
    ListeningService listeningService;
    PlaySound soundAlert;
    public Intent intent1;
    public Intent intent2;
    Integer alarmImages[];

    String alertTextMessages[];
    String imminentTextMessages[];
    Context context;

    public boolean on;

    int sendAlarm;

    public AndroidUDP() {

    }


    public AndroidUDP(MainActivity r, Context c)
    {
        on = false;
        sendAlarm = alerts.ALERT_CLEAR.ordinal();
        mainActivity = r;
        context = c;


        alarmImages = new Integer[19];
        alarmImages[alerts.ALERT_CLEAR.ordinal()] = R.drawable.image_all_clear;
        alarmImages[alerts.ALERT_FCW_IMMINENT.ordinal()] = R.drawable.image_fcw_imminent;
        alarmImages[alerts.ALERT_DNPW_IMMINENT.ordinal()] = R.drawable.image_dnpw_imminent;
        alarmImages[alerts.ALERT_IMA_IMMINENT_LEFT.ordinal()] = R.drawable.image_ima_imminent_left;
        alarmImages[alerts.ALERT_IMA_IMMINENT_RIGHT.ordinal()] = R.drawable.image_ima_imminent_right;
        alarmImages[alerts.ALERT_CLW_IMMINENT.ordinal()] = R.drawable.image_clw_imminent;
        alarmImages[alerts.ALERT_LCW_IMMINENT_LEFT.ordinal()] = R.drawable.image_lcw_imminent_left;
        alarmImages[alerts.ALERT_LCW_IMMINENT_RIGHT.ordinal()] = R.drawable.image_lcw_imminent_right;
        alarmImages[alerts.ALERT_BSW_IMMINENT_LEFT.ordinal()] = R.drawable.image_bsw_imminent_left;
        alarmImages[alerts.ALERT_BSW_IMMINENT_RIGHT.ordinal()] = R.drawable.image_bsw_imminent_right;

        alarmImages[alerts.ALERT_FCW_ADVISORY.ordinal()] = R.drawable.image_fcw_advisory;
        alarmImages[alerts.ALERT_DNPW_ADVISORY.ordinal()] = R.drawable.image_dnpw_advisory;
        alarmImages[alerts.ALERT_IMA_ADVISORY_LEFT.ordinal()] = R.drawable.image_ima_advisory_left;
        alarmImages[alerts.ALERT_IMA_ADVISORY_RIGHT.ordinal()] = R.drawable.image_ima_advisory_right;
        alarmImages[alerts.ALERT_BSW_ADVISORY_LEFT.ordinal()] = R.drawable.image_bsw_advisory_left;
        alarmImages[alerts.ALERT_BSW_ADVISORY_RIGHT.ordinal()] = R.drawable.image_bsw_advisory_right;
        alarmImages[alerts.ALERT_LTA_ADVISORY_LEFT.ordinal()] = R.drawable.image_lta_advisory_left;
        alarmImages[alerts.ALERT_LTA_ADVISORY_RIGHT.ordinal()] = R.drawable.image_lta_advisory_right;
        alarmImages[alerts.ALERT_LTA_ADVISORY_OPPOSITE.ordinal()] = R.drawable.image_lta_advisory_opposite;

        alertTextMessages = new String[19];
        alertTextMessages[alerts.ALERT_CLEAR.ordinal()] = "ALL CLEAR";
        alertTextMessages[alerts.ALERT_FCW_ADVISORY.ordinal()] = "FORWARD COLLISION ALERT";
        alertTextMessages[alerts.ALERT_DNPW_ADVISORY.ordinal()] = "DO NOT PASS ALERT";
        alertTextMessages[alerts.ALERT_IMA_ADVISORY_LEFT.ordinal()] = "LEFT INTERSECTION MOVEMENT ALERT";
        alertTextMessages[alerts.ALERT_IMA_ADVISORY_RIGHT.ordinal()] = "RIGHT INTERSECTION MOVEMENT ALERT";
        alertTextMessages[alerts.ALERT_BSW_ADVISORY_LEFT.ordinal()] = "LEFT BLIND SPOT ALERT";
        alertTextMessages[alerts.ALERT_BSW_ADVISORY_RIGHT.ordinal()] = "RIGHT BLIND SPOT ALERT";
        alertTextMessages[alerts.ALERT_LTA_ADVISORY_LEFT.ordinal()] = "LEFT LEFT TURN ASSIST ALERT";
        alertTextMessages[alerts.ALERT_LTA_ADVISORY_RIGHT.ordinal()] = "RIGHT LEFT TURN ASSIST ALERT";
        alertTextMessages[alerts.ALERT_LTA_ADVISORY_OPPOSITE.ordinal()] = "OPPOSITE LEFT TURN ASSIST ALERT";

        imminentTextMessages = new String[19];
        imminentTextMessages[alerts.ALERT_FCW_IMMINENT.ordinal()] = "FORWARD COLLISION IMMINENT";
        imminentTextMessages[alerts.ALERT_DNPW_IMMINENT.ordinal()] = "DO NOT PASS IMMINENT";
        imminentTextMessages[alerts.ALERT_IMA_IMMINENT_LEFT.ordinal()] = "LEFT INTERSECTION MOVEMENT IMMINENT";
        imminentTextMessages[alerts.ALERT_IMA_IMMINENT_RIGHT.ordinal()] = "RIGHT INTERSECTION MOVEMENT IMMINENT";
        imminentTextMessages[alerts.ALERT_CLW_IMMINENT.ordinal()]= "CONTROL LOSS IMMINENT";
        imminentTextMessages[alerts.ALERT_LCW_IMMINENT_LEFT.ordinal()] = "IMMINENT LEFT LANE CHANGE";
        imminentTextMessages[alerts.ALERT_LCW_IMMINENT_RIGHT.ordinal()] = "IMMINENT RIGHT LANE CHANGE";
        imminentTextMessages[alerts.ALERT_BSW_IMMINENT_LEFT.ordinal()] = "IMMINENT LEFT BLIND SPOT";
        imminentTextMessages[alerts.ALERT_BSW_IMMINENT_RIGHT.ordinal()] = "IMMINENT RIGHT BLIND SPOT";


    }

    ////creating broadcast receiver
    private BroadcastReceiver heard_reception = new BroadcastReceiver()
    {
        @Override
        public void onReceive(Context context, Intent intent)
        {

            //Toast.makeText(context, P, Toast.LENGTH_SHORT).show();

            String P = listeningService.the_alarm_S;
            String A[] = P.split("!!!");


            if(Integer.valueOf(A[1]) >= 0 && Integer.valueOf(A[1]) <= alerts.ALERT_CLEAR.ordinal() && on)
            {
                if(sendAlarm == alerts.ALERT_CLEAR.ordinal())
                {
                    sendAlarm = Integer.valueOf( A[1] );
                    repeatAlarm();       // start recursive function
                }
                else
                {
                    sendAlarm = Integer.valueOf( A[1] );

                    // remove at end
                    repeatAlarm();
                }
            }
        }
    };

    public void repeatAlarm()
    {
        MainActivity.mImageView.setImageResource( alarmImages[sendAlarm] );

        Animation mAnimation = new AlphaAnimation(0.0f, 1.0f);
        mAnimation.setDuration(50);
        mAnimation.setStartOffset(20);
        mAnimation.setRepeatCount(Animation.INFINITE);
        mAnimation.setRepeatMode(Animation.REVERSE);
        MainActivity.mBlinkView.startAnimation(mAnimation); //animation value

        MainActivity.mBlinkView.setText(imminentTextMessages[sendAlarm]);
        MainActivity.mTextView.setText(alertTextMessages[sendAlarm]);



        if(sendAlarm >= 1 || sendAlarm <= 9 && sendAlarm != 0) {
            MainActivity.mTextView.setText(alertTextMessages[sendAlarm]);
        } else {
            MainActivity.mBlinkView.setText(imminentTextMessages[sendAlarm]);
        }


        //soundAlert.sendAlarm2 = sendAlarm;
        soundAlert.playIt(sendAlarm);


        //Toast.makeText(context, String.valueOf(sendAlarm), Toast.LENGTH_SHORT).show();

    }


    public void stopAll()
    {

        soundAlert.stopIt();
    }


    public void doStuff(MainActivity r, IntentFilter filter)
    {
        mainActivity = r;

        mainActivity.bindService(intent1, serviceConn1, Context.BIND_AUTO_CREATE);
        mainActivity.registerReceiver(heard_reception, filter);

        mainActivity.bindService(intent2, serviceConn2, Context.BIND_AUTO_CREATE);

    }


    public void startIt()
    {

        listeningService = new ListeningService();
        listeningService.intent1 = new Intent(this, ListeningService.class);
        startService(listeningService.intent1);

        listeningService.continueListening();

    }

    ////creating service connection
    ServiceConnection serviceConn1 = new ServiceConnection()
    {
        //service unbound, release from memory
        @Override
        public void onServiceDisconnected(ComponentName name)
        {

            listeningService = null;
        }

        //service is bound, start it's work
        @Override
        public void onServiceConnected(ComponentName name, IBinder service)
        {
            listeningService = ((ListeningService.LocalBinder)service).getService();

        }
    };

    ////creating service connection
    ServiceConnection serviceConn2 = new ServiceConnection()
    {
        //service unbound, release from memory
        @Override
        public void onServiceDisconnected(ComponentName name)
        {

            soundAlert = null;
        }

        //service is bound, start it's work
        @Override
        public void onServiceConnected(ComponentName name, IBinder service)
        {
            soundAlert = ((PlaySound.LocalBinder)service).getService();
        }
    };

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

}
I hope none of you get cancer after reading this.

They havent had real programmers in the company until like March and now that we have some down time theyre coming to us to fix their broken code that's due tomorrow at 3p pls can u make this work!!!

My existence is pain

FAT32 SHAMER fucked around with this message at 23:26 on Jul 13, 2017

FAT32 SHAMER
Aug 16, 2012



b0lt posted:

You're sending imminent forward collision alerts via UDP?

That's exactly what I said

baka kaba posted:

I haven't done a lot with services and there's a bunch of weird stuff in there (what's listeningService.intent1, I don't see that field? What's with creating a ListeningService with new?) that should stop it running at all, I'm guessing you're trying to cut out code so it's not all over the internets but it's hard to look through

You could try this

Java code:

public void startIt()
{
    Intent intent = new Intent(this, ListeningService.class);
    startService(intent);
}

That's generally how it goes
  • you call startService() with an an intent for the service class you want
  • onStartCommand() gets called in that service
in your onStartCommand you're already calling continueListening(), so no need to do it in the startIt() method. (In fact right now it's getting called on the instance you newed which isn't initialised as a service

The binding stuff... it's referring to intent1 which isn't initialised anywhere, and maybe that's the mysterious listeningService.intent1 and... yeah this is hard to follow. It's hard to know if this will break anything, you can always try it



Otherwise get your debugger open, stick a watch on that listeningService variable that's going null and see what's doing it :shrug:

Thanks for this, I'll see how it goes tomorrow when I get in

FAT32 SHAMER
Aug 16, 2012



I decided to say gently caress it and instead of trying to use the listener as a service, just use an AsyncTask as an inner class of the main activity because it's a single activity app and there's no reason to overcomplicate it like the original guy did. Stupid question time: in the onPostExecution I understand that this would be where I set the text/image of my TextView and ImageView. According to this stackoverflow question

quote:

If this is an inner class of your Activity then initialize the View in the Activity then update it in your task as described above.
I'm assuming he's saying "initialize the MainActivity's View in the inner class" but i can't parse this because it's friday and i'm retarded

FAT32 SHAMER
Aug 16, 2012



baka kaba posted:

They're saying hold the reference in the activity class, initialise it with findViewById when the activity is setting up (like in onCreateView) and then just refer to that from your AsyncTask

If I reference it from onCreate that causes a static/nonstatic error, wouldn't it?

FAT32 SHAMER
Aug 16, 2012



baka kaba posted:

Nah you have a non-static TextView field or whatever in your activity, assign it when you can grab a reference to the actual View after it's been inflated. onCreateView is the best time to do this - inflate your layout, do your findViewById stuff to pluck out the Views you want references to, then return the inflated layout view as usual

Then with your AsyncTask as an inner class of the activity it can access that reference. Just make sure it doesn't try to access it before you've actually set it - so start your task after the view field is assigned

(You can't grab the view in onCreate because you haven't inflated your layout yet)

Ahhh that explains a lot. I always forget about the difference between the two

FAT32 SHAMER
Aug 16, 2012



So I successfully got my AsyncTask working and it receives the data that is pinged to it and changes the ImageView and TextView based on what the message says when parsed. It works almost exactly how I'd like it to except for one problem: you have to press the button multiple times, which means if the packet data changes, you have to be clicking it quite frequently to get the update. I attempted to use a ToggleButton with a while loop to continuously run the AsyncTask while the ToggleButton had .isChecked = true, however as you can imagine this caused it to crash due to what I assume was too much data on the UI thread.

So based on this, I believe that I need to rewrite this as a service so that it can run on its own thread and not kill everything. This is what my AsyncTask currently looks like:

Java code:
public class StartListening extends AsyncTask<Void, Void, String> {

    String messageContents;
    AlertAssignments alertAssignments;
    PlaySound soundAlert;
    int counter1;
    int the_alarm;
    int sendAlarm;
    public String the_alarm_S;
    Context contextGUI;
    boolean mBound = false;

    PlaySound mPlaySound;
    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            PlaySound.LocalBinder binder = (PlaySound.LocalBinder) service;
            mPlaySound = binder.getService();
            mBound = true;
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.e("UDP", "Service Disconnected");
            mBound = false;
        }
    };



    public StartListening(Context callerClass) {

        contextGUI = callerClass;
    }


    @Override
    protected void onPreExecute() {

        super.onPreExecute();
        //Intent intent = new Intent(this, PlaySound.class);
        //bindService(intent, mConnection, Context.BIND_AUTO_CREATE);

    }

    @Override
    protected String doInBackground(Void... params)
    {
        int portNumber = 12001;
        DatagramSocket serverSocket;

        try
        {
            serverSocket = new DatagramSocket(portNumber);
            serverSocket.setReuseAddress(true);
            messageContents = "";

            byte[] receiveData = new byte[1024];
            DatagramPacket receivePacket = new DatagramPacket(receiveData, 1024);
            Log.e("UDP", "Waiting for UDP broadcast");
            try
            {
                serverSocket.receive(receivePacket);
                //serverSocket.close();
            }
            catch (IOException e)
            {
                e.printStackTrace();
                return "NA 1!!!1!!!NA 1";
            }
            messageContents = new String( receivePacket.getData() );
            Log.e("UDP", "Received UDP Broadcast with message: " + messageContents);

            serverSocket.close();

            return messageContents;
        }
        catch (SocketException e)
        {
            e.printStackTrace();
            return "NA 2!!!1!!!NA 2";
        }
    }

    TextView mTextView;
    TextView mBlinkView;
    ImageView mImageView;

    @Override
    protected void onPostExecute(String result) {
        // send results from UDP wait back to parent thread
        alertAssignments = new AlertAssignments();
        soundAlert = new PlaySound();


        mImageView = (ImageView) ((MainActivity) contextGUI).findViewById(R.id.image_view);
        mTextView = (TextView) ((MainActivity) contextGUI).findViewById(R.id.alert_text);
        mBlinkView = (TextView) ((MainActivity) contextGUI).findViewById(R.id.blinking_text);
        Animation mAnimation = new AlphaAnimation(0.0f, 1.0f);
        mAnimation.setDuration(50);
        mAnimation.setStartOffset(20);
        mAnimation.setRepeatCount(Animation.INFINITE);
        mAnimation.setRepeatMode(Animation.REVERSE);
        mBlinkView.startAnimation(mAnimation); //animation value

        super.onPostExecute(result);
        the_alarm_S = messageContents;
        String parseMessage[] = the_alarm_S.split("!!!");
        Log.e("UDP", "Parsed message with value " + parseMessage[1]);

        /*if(Integer.valueOf(parseMessage[1]) >= 0 && Integer.valueOf(parseMessage[1]) <= AlertAssignments.alerts.ALERT_ALL_CLEAR.ordinal()) {
            if(sendAlarm == AlertAssignments.alerts.ALERT_ALL_CLEAR.ordinal()) {
                sendAlarm = Integer.valueOf(parseMessage[1]);
                repeatAlarm(sendAlarm);
            }
            else {
                sendAlarm = Integer.valueOf(parseMessage[1]);
                repeatAlarm(sendAlarm);
            }
        }
        */
        Integer parsedMessage = Integer.valueOf(parseMessage[1]);
        mImageView.setImageResource(alertAssignments.alarmImages[parsedMessage]);

        if(parsedMessage >= 10 && parsedMessage <= 19 && parsedMessage != 0) {
            mTextView.setText(alertAssignments.alertTextMessages[parsedMessage]);
        } else {
            mBlinkView.setText(alertAssignments.alertTextMessages[parsedMessage]);
        }
        //soundAlert.playIt(parsedMessage);


    }

}
Stupid question time: am I able to stick this AsyncTask into a Service and have the service run it constantly without clogging up the UI thread? or will I run into the same problem? Instead of dealing with the dumbass's original code I just rewrote the whole thing which took two days so hopefully I'm able to salvage some of that time I spent

edit: I guess this would work? https://gist.github.com/finnjohnsen/3654994

FAT32 SHAMER fucked around with this message at 17:15 on Jul 18, 2017

FAT32 SHAMER
Aug 16, 2012



Volmarias posted:

Without looking at or caring about any of your code, I'd like to point out that you can start a new thread from an Activity, it doesn't actually need to live in a service unless you'd like it to persist when the Activity goes away for some reason.

Create a static class that implements Runnable. Have it's run method check for some member value like isRunning (give it the volatile keyword) and return when false. Have a method that sets isRunning to false, call it in onPause. Pass a Handler reference to your constructor, and pass messages to the handler when you need to update the UI. Create an instance and start a new thread with it in onResume (keep a reference to it so that you can end it in onPause)

Your Handler will update on the UI thread when it receives a message, so update your text fields with whatever.

Refactoring this to be fault tolerant is left as an exercise to the reader.


baka kaba posted:

Yeah if you're rewriting it I'd ditch the AsyncTask which is really meant as a quick way to do something off the UI thread and deliver the result on it.

I don't really know much about UDP or preventing car accidents, but it feels like you might want something running in its own thread catching UDP messages and turning them into events to put on a queue to be consumed, which is basically what Volmarias is saying with the 'post messages to the UI thread Handler' pattern

You should probably read this if you haven't already - it talks about what services are and the ways you use them. Briefly the point is to have a component that can run independently of your app (and could even be started by other apps), can manage its own lifetime, and is less likely to be killed by the system when it's low on memory. If you don't care about any of those things, and you just want it to scan for UDPs when the app is in the foreground and stop when it's not, then you don't need to abstract to a service

This covers the whole handler/message thing which is fine for foreground stuff. The CodePath tutorials are generally pretty good!

oh yeah I guess that's a great point, no need to abstract something like this when I can just have it run on it's own since nothing else will be using it. thanks for the tips and for putting up with my dumbass questions :tipshat:

FAT32 SHAMER
Aug 16, 2012



Volmarias posted:

No problem.

As an aside, is there a reason why you're setting up and tearing down the connection on every button press? Just setting it up once and leaving it open means you can just view the values. Does the broadcast happen too frequently and with enough variance for that to be feasible?

That was the idea, the UDP messages are sent every 1/100th of a second and constantly changing due to a vehicle possibly changing position switching a potential alert from standard to imminent. I had it on every button press because the AsyncTask otherwise seems to crash the app when using a Togglebutton + while loop to check if the Togglebutton.isChecked() then running the AsyncTask from that

Previous to your comments I wrote a service to handle the UDP traffic which runs on its own thread, and now that I have it binding it's not receiving the traffic so I'm making sure that i have it pointed at the right ports and all that jazz.

FAT32 SHAMER fucked around with this message at 22:16 on Jul 18, 2017

FAT32 SHAMER
Aug 16, 2012



Volmarias posted:

Without looking at or caring about any of your code, I'd like to point out that you can start a new thread from an Activity, it doesn't actually need to live in a service unless you'd like it to persist when the Activity goes away for some reason.

Create a static class that implements Runnable. Have it's run method check for some member value like isRunning (give it the volatile keyword) and return when false. Have a method that sets isRunning to false, call it in onPause. Pass a Handler reference to your constructor, and pass messages to the handler when you need to update the UI. Create an instance and start a new thread with it in onResume (keep a reference to it so that you can end it in onPause)

Your Handler will update on the UI thread when it receives a message, so update your text fields with whatever.

Refactoring this to be fault tolerant is left as an exercise to the reader.

So I've done this and as it turns out sending messages to the UI every 1/100th of a second will crash the UI thread no matter what I do. The listening thread is running fine and it processes all of the data thrown at it just how i'd like. I've been trying to figure out a way to update the UI every 1 second (since this is just a monitoring app to make sure that a vehicle is sending the correct messages and we're not testing for timing)

I tried using a while loop when ToggleButton.isChecked = true to call the method in the MainActivity that fetches mListenerService.getParsedMessage and updates the TextView but that obviously locked the UI thread due to the while loop constantly looping while the button isChecked, then I tried putting the while loop in its own thread so that I can sleep it for a second but surprise surprise that didnt work either since now I can't call a method that works on the UI thread from a different thread. I've been messing with Handlers for a little bit but it still crashes the UI thread

Java code:

mToggleButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, final boolean isChecked) {
                final Handler handler = new Handler();
                handler.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        while(isChecked) {
                            readFromService();
                        }
                    }
                }, 1000);
            }
        });

How should I got about updating the UI without crashing it?

Instead of trying to call the method that works on the UI thread, should set a global String to the value of the mListenerService.getParsedMessage and update the objects from that? I feel like I'm on the cusp of fully understanding this after reading about all this stuff last night when I got home

Edit: better way to phrase it: how can I call a method that works on the UI less often than however quickly it's currently being called

FAT32 SHAMER fucked around with this message at 23:12 on Jul 19, 2017

FAT32 SHAMER
Aug 16, 2012



Volmarias posted:

Posting to the UI thread every 10ms won't cause a crash. You're doing something else wrong. Post the stack trace and relevant code along with line numbers.

In case you're trying to update the UI from the background thread, don't. That's what the Handler is for. You post a message to the handler, and the handler's method that you override (you read the docs right?) will run on the UI thread.

There's no stack trace, the UI simply stops responding then five seconds later I get the "your app is unresponsive" dialog box

MainActivity
Java code:
public class MainActivity extends Activity {

    AlertAssignments mAlertAssignments;



    Button startListeningButton;

    boolean started;
    int counter;
    boolean mBound = false;
    Context context;
    ListenerService mListenerService;

    TextView mTextView;
    TextView mBlinkView;
    ImageView mImageView;
    ToggleButton mToggleButton;


    private StartListening _StartListeningTask;
    String messageFromService = "";




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

        //start listener service
        Intent listenerServiceIntent = new Intent(MainActivity.this, ListenerService.class);
        this.bindService(listenerServiceIntent, mConnection, Context.BIND_AUTO_CREATE);

       
        mImageView = (ImageView) findViewById(R.id.image_view);
        mTextView = (TextView) findViewById(R.id.alert_text);
        mBlinkView = (TextView) findViewById(R.id.blinking_text);
        Animation mAnimation = new AlphaAnimation(0.0f, 1.0f);
        mAnimation.setDuration(50);
        mAnimation.setStartOffset(20);
        mAnimation.setRepeatCount(Animation.INFINITE);
        mAnimation.setRepeatMode(Animation.REVERSE);
        mBlinkView.startAnimation(mAnimation); //animation value
        mAlertAssignments = new AlertAssignments();


        mToggleButton = (ToggleButton) findViewById(R.id.toggle_button);
        mToggleButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, final boolean isChecked) {
		while(isChecked) {
			readFromService(); // <---THIS is where I believe it's hanging up: the while loop is causing the UI/Main thread to lock while it continuously checks that the ToggleButton is pressed
            }
        });



    }
    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName className, IBinder service) {
            ListenerService.LocalBinder binder = (ListenerService.LocalBinder) service;
            mListenerService = binder.getService();
            mBound = true;
            if(mBound) {
                Log.e("UDP", "Service has been bound successfully");
            }
            else {
                Log.e("UDP", "Service has not been bound");
            }

            readFromService();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            mBound = false;
        }
    };

    @Override
    protected void onStart() {
        super.onStart();

    }

    @Override
    protected void onResume() {
        super.onResume();
    }

    @Override
    protected void onStop() {
        super.onStop();
        //unbind from service
        if(mBound) {
            this.unbindService(mConnection);
            mBound = false;
        }

    }

    private void readFromService() {
        try {
            Log.e("Main UDP", "MainActivity received message from ListenerService of " + mListenerService.getParsedMessage());
            Integer parsedMessage = Integer.valueOf(mListenerService.getParsedMessage());

            mImageView.setImageResource(mAlertAssignments.alarmImages[parsedMessage]);

            if(parsedMessage >= 10 && parsedMessage <= 19 && parsedMessage != 0) {
                mTextView.setText(mAlertAssignments.alertTextMessages[parsedMessage]);
            } else {
                mBlinkView.setText(mAlertAssignments.alertTextMessages[parsedMessage]);
            }


        } catch(NumberFormatException e) {
            e.printStackTrace();
        } catch(NullPointerException e) {
            e.printStackTrace();
        }

    }


}


I commented where I believe the issue is coming from.

ListenerService.
Java code:
public class ListenerService extends Service{
    public String the_alarm_S;
    public String parsedMessage = "";
    private final IBinder mBinder = new LocalBinder();


    public class LocalBinder extends Binder {
        ListenerService getService() {
            return ListenerService.this;
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    DatagramSocket socket;

    Thread UDPBroadcastThread;

    void startListenForUDPBroadcast() {
        UDPBroadcastThread = new Thread(new Runnable() {
            public void run() {
                try {
                    while (shouldRestartSocketListen) {
                        try {
                            socket = new DatagramSocket(12001);
                            socket.setReuseAddress(true);
                            String message = "";

                            byte[] recvBuf = new byte[1024];
                            DatagramPacket packet = new DatagramPacket(recvBuf, 1024);
                            Log.e("Listener UDP", "Waiting for UDP broadcast");
                            try {
                                socket.receive(packet);
                                Log.e("Listener UDP", "Received Packet");
                            } catch (IOException e) {
                                e.printStackTrace();

                            }
                            message = new String(packet.getData());

                            Log.e("Listener UDP", "Got UDB broadcast message: " + message);

                            setParsedMessage(message);

                            if(socket != null) {
                                socket.close();
                            }
                        } catch (SocketException e) {
                            e.printStackTrace();
                        }
                    }
                    //if (!shouldListenForUDPBroadcast) throw new ThreadDeath();
                } catch (Exception e) {
                    Log.i("Listener UDP", "no longer listening for UDP broadcasts cause of error " + e.getMessage());
                }
            }
        });
        UDPBroadcastThread.start();
    }

    private Boolean shouldRestartSocketListen = true;

    private void setParsedMessage(String messageContents) {
        the_alarm_S = messageContents;
        String parseMessage[] = the_alarm_S.split("!!!");
        Log.e("Listener UDP", "Parsed message with value " + parseMessage[1]);
        parsedMessage = parseMessage[1];

    }
    public String getParsedMessage() {
        return parsedMessage;
    }

    private void stopListen() {
        shouldRestartSocketListen = false;
        if(socket != null) {
            socket.close();
        }
    }

    @Override
    public void onCreate() {
        startListenForUDPBroadcast();
    }

    @Override
    public void onDestroy() {
        stopListen();
    }


    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        shouldRestartSocketListen = true;
        startListenForUDPBroadcast();
        Log.i("UDP", "Service started");
        return START_STICKY;
    }
}

ListenerService receives every UDP packet and prints them in logcat. Maybe it's the way i implemented my readFromService() method? instead of retrieving the value parsed from the message via mListenerService.getParsedMessage() should I be passing the string via a bundle or a handler or something similar?

FAT32 SHAMER
Aug 16, 2012



Volmarias posted:

You have a while loop in your listener. Why? :negative:
i am not a clever man :saddowns:

Volmarias posted:

You've diagnosed the issue. The problem is that you need to return from the listener call so that the rest of the work on the UI thread, like the drawing, can happen.

Using a service overcomplicates this for no tangible gain. Get rid of it, just use a background thread.

I'm gonna mess around with this idea and do a runnable class since making it a service seems to also make it a pain in the dick

kitten smoothie posted:

Even after you get that off the main thread, why are you closing and re-opening the socket every time you get a message? Just leave it open and keep reading infinitely. It'll block the listener thread while waiting for data to come in, so when you're done close the socket from outside that thread. Ignore the resulting IOException, because you'll get one from closing the socket out from under a blocking receive call.
Where is it opening and closing the socket? I thought once the thread was running, as long as the socket != null it would stay open, unless calling the public method from the service opens and closes it every time...

baka kaba posted:

You want something like this
Java code:
    static class MagicCarBrain implements Runnable {

        volatile boolean isRunning = false;
        final Handler uiThreadHandler;

        MagicCarBrain(Handler uiThreadHandler) {
            this.uiThreadHandler = uiThreadHandler;
        }

        @Override
        public void run() {
            while (isRunning) {
                String data = readFromService();
                uiThreadHandler.post(() -> myTextView.setText(data));
                Thread.sleep(MESSAGE_DELAY);
            }
        }
    }
So you create that, giving it a handler for your main thread, which you can get with new Handler(Looper.getMainLooper()). (A Looper maintains a message queue for a thread, a Handler handles delivering messages to it and running them.) When you run this on another thread you create (new Thread(myMagicCarBrain) and then start() it), it keeps running that code, posting messages to the UI thread

How you want to throttle it honestly depends on what the hell you're doing. I'm guessing actually sleeping the thread is bad because you need to be constantly watching for messages coming in, never missing any. I don't know if you'll actually overflow the message queue doing this (?), but it's probably a safer option to add some kind of throttling policy just in case you get a deluge of UDP packets for some reason. The message pool only has 50 message objects by default too, I think, and the system uses them too, which might be why you're crashing

But what do you do if you're producing messages faster than you consume them? How do you prioritise them, how long does an OH poo poo message stay around for, are there any others that would override it? Do you drop the original message or show it later?

Honestly I'd be tempted to get a basic polling thing going on in your activity like this
Java code:
volatile boolean isRunning = false;
Handler uiThreadHandler = new Handler(Looper.getMainLooper());
CarInquirer updater = new CarInquirer();

private class MagicCarBrain implements Runnable {

    volatile CarState currentCarState;

    @Override
    public void run() {
        while (isRunning) {
            Object data = readFromService();
            // update currentCarState
        }
    }

    CarState getLatestData() {
        // maybe do some logic here, since you know stuff has been seen now
        return currentCarState;
    }
}

private class CarInquirer implements Runnable {

    @Override
    public void run() {
        updateUi(magicCarBrain.getLatestData());
        if (isRunning) {
            ((Handler)uiThreadHandler).postDelayed(this, UPDATE_DELAY);
        }
    }
}

// starting
if (!isRunning) {
    isRunning = true;
    new Thread(new MagicCarBrain()).start();
    updater.run();
}

// stopping
isRunning = false; // both runnables stop looping, which ends the thread and the handler re-post
It's quick and hacky but I'm tired, basically you have your service on a thread reading and storing data at its own speed. The UI thread keeps posting a runnable to itself (through a handler for the main thread) that makes it poll the service for data every so often. When you need to stop everything, you can turn off the running flag which ends the looping behaviour for both.

I mean it's buggy (stop and start quick enough and you'll add another runnable to the polling queue, the first won't have seen the flag going false) and you'll have to worry about atomicity if you start doing complicated stuff with the data you're providing, but it's all fixable if you care enough. Just trying to give an example here

This might help too

I typed bolleazn while writing this, I think it's my best typo yet
I'll also play around with this

FAT32 SHAMER
Aug 16, 2012



So idk how terrible this is, but I figured out a stupidly simple way to redraw the textviews and imageview all the time and it works extremely well. I simply used Animation's applyTransformation. I've had the app running for about an hour with no issue, so fingers crossed it is good to go.

edit: here's the code

Java code:
startListeningButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mSoundAlertService.playIt(Integer.valueOf(mListenerService.getParsedMessage()));
                final Animation objectAnimation = new Animation() {
                    @Override
                    protected void applyTransformation(float interpolatedTime, Transformation t) {
                        super.applyTransformation(interpolatedTime, t);

                        try {
                            Log.e("Main UDP", "MainActivity received message from ListenerService of " + mListenerService.getParsedMessage());
                            Integer parsedMessage = Integer.valueOf(mListenerService.getParsedMessage());

                            mImageView.setImageResource(mAlertAssignments.alarmImages[parsedMessage]);


                            if(parsedMessage >= 10 && parsedMessage <= 19) {
                                mBlinkView.setText("");
                                mTextView.setText(mAlertAssignments.alertTextMessages[parsedMessage]);
                            } else {
                                mTextView.setText("");
                                mBlinkView.setText(mAlertAssignments.alertTextMessages[parsedMessage]);
                            }

                        } catch(NumberFormatException e) {
                            e.printStackTrace();
                        } catch(NullPointerException e) {
                            e.printStackTrace();
                        }

                    }
                };

                objectAnimation.setAnimationListener(new Animation.AnimationListener() {
                    @Override
                    public void onAnimationStart(Animation animation) {
                        Animation blinkTextAnimation = new AlphaAnimation(0.0f, 1.0f);
                        blinkTextAnimation.setDuration(50);
                        blinkTextAnimation.setStartOffset(20);
                        blinkTextAnimation.setRepeatCount(Animation.INFINITE);
                        blinkTextAnimation.setRepeatMode(Animation.REVERSE);
                        mBlinkView.startAnimation(blinkTextAnimation);
                    }
                    @Override
                    public void onAnimationRepeat(Animation animation) {

                    }
                    @Override
                    public void onAnimationEnd(Animation animation) {

                    }

                });
                objectAnimation.setRepeatCount(Animation.INFINITE);
                objectAnimation.setDuration(1000);

                mImageView.startAnimation(objectAnimation);
                mTextView.startAnimation(objectAnimation);
                mBlinkView.startAnimation(objectAnimation);

            }
        });
it just works

FAT32 SHAMER fucked around with this message at 18:33 on Jul 20, 2017

FAT32 SHAMER
Aug 16, 2012



Volmarias posted:

:psyduck:

It's not like you need to apply some sort of trick to make UI things update, just post things on the UI thread. You're creating some sort of bizarre horror, and I hope you at least comment this callback with "// I don't actually understand how to update the UI, this callback isn't necessary"


If you're really having trouble putting things on the UI thread, use Activity.runOnUiThread with a Runnable of things to do in the UI thread (not networking, don't make it a loop)

Like, would you like to just cheat and have us write this for you?

nonono it's all good, I realized that while this works it's a terrible way to do it and I implemented the handler as was shown by baka. I have much to learn :negative:

FAT32 SHAMER
Aug 16, 2012



kitten smoothie posted:

Your listen thread is a while loop.

At the top of the loop it opens a socket and blocks until a message comes in. When a message comes in, it dispatches the data received, then you close the socket. Back to the top and repeat. You're closing and reopening for every message.

The socket != null check in that loop is kind of useless where it is, because if the socket were null it would've NPE'd earlier on when you called receive on it.
:doh:
I'm not sure why i didnt recognize this but that explains a few questions i had in the back of my mind

baka kaba posted:

Honestly after the initial :eyepop: about doing it through animations, I realised you're basically updating views on a tick and one of them is animating a fade anyway. So you're really polling for data when the animation has faded out, so you can switch the content while it's invisible.

From a purely informational UI standpoint I don't think that's too bad? It's basically a self-updating display that's pulling data when it's convenient, based on the animation state. It'd be more awkward to coordinate that if you're pushing data instead

This was my initial thinking as well. I'm not sure how terrible it is either but i'd rather do something correctly and learn it so I know for the future instead of do a potentially insane hack to make it work

Do you guys have any resources with projects I can work through in my spare time? I got about halfway through the big nerd ranch android book i bought about two years ago and while it was insightful, I wasnt sure how useful some of the stuff would be due to a lot of deprecated things (like apparently SoundPool)

FAT32 SHAMER
Aug 16, 2012



kitten smoothie posted:

The BNR book is still probably one of the best resources for "I can write some Java but need to understand the Android framework." They put out a new edition in January so I'd imagine the outdated stuff is gone now (for instance it looks like the only thing deprecated about SoundPool is the constructor method, not the framework class itself).

I'd also start looking through some of Dan Lew's RxJava posts (start from the very beginning) so you can avoid ever touching an AsyncTask ever again.

Nice! Thanks for this, and thanks for all the help and insight you guys have given me in the last two weeks. Pray for me because i'm the only person on my team familiar with Java and Android (they all did C# in school and I did Java datastructures/android apps and iOS apps) :pray:

FAT32 SHAMER
Aug 16, 2012



Volmarias posted:

Java is pretty similar to C# (I think of C# as Java With Hindsight), so transitioning isn't terrible.

The official Android guides and tutorials aren't terrible anymore.

That's what I told them. I guess they'll learn it if they absolutely have to, but since we work in automotive their big focus is C/C++ and C#

FAT32 SHAMER
Aug 16, 2012



Ok guys I have a super dumb question (again). The last time I built a [complicated multi-activity] app, Fragments didn't exist and everything was straight forward. I've built a NavDrawer based on this tutorial that i found that isn't garbage and it works great A++. Now my question is, do i build my UI in fragment_layouts for each fragment or do I have to do some kind of interface so that the fragment "becomes", for lack of a better word, an activity.

For instance, here is the drawer that I've built.


(i'm fixing the padding at the top pls ignore)

If I click on "Completed", it crashes if it extends AppCompatActivity but it is fine when it extends Fragment, which I guess makes sense. The tutorial has my MainActivity extending AppCompatActivity, however based on the android developer I should be extending FragmentActivity. Should I be extending both? Should I do some kind of fuckery to make the navdrawer launch activities instead of fragments?

Thanks :)

FAT32 SHAMER
Aug 16, 2012



baka kaba posted:

AppCompatActivity inherits from FragmentActivity - use the AppCompat version because it basically gives you a lot of the modern look and other stuff in older versions of Android.

If you want to launch an Activity you do it the usual way
Java code:
Intent intent = new Intent(context, NextActivity.class);
getActivity().startActivity(myIntent);
where if you're in a fragment you'll need to get its activity (you can use that for the context too). You can add an Extra to the intent if you want to say pass a fragment name (if your activity is meant to be reused and host different fragments)


The way you're meant to use Activities and Fragments is you have an Activity, and you inflate a layout in it as usual. That layout can include Fragments in the XML, or you can include a container like a FrameLayout with an id you can find, and stick the fragment in there programmatically. If you do it that way you can swap the fragments in and out without having to go to a new Activity

So a fragment is like a component you can add to the activity as and when you need it, and maybe reuse it in multiple places. It can have its own layout (so it provides part of the UI) or it can just be a 'headless' fragment that has some useful functionality but doesn't appear visually. What you'll generally do is have an Activity with some main UI (like the toolbar at the top), define your fragments separately, and then add them into the Activity to complete your UI. So you can have a fragment that mostly (or completely) fills the whole Activity's view, yeah!

Read this basically
http://guides.codepath.com/android/Creating-and-Using-Fragments
Also some people hate fragments and say you should never use them, yolo

Awesome, thanks a lot :)

FAT32 SHAMER
Aug 16, 2012



kitten smoothie posted:

Samesies. I guess if you get off on dealing with bugs from multiple layers of lifecycles they're cool.



ok holy poo poo am i glad I just finished converting my fragments to activities :eyepop:

FAT32 SHAMER
Aug 16, 2012



My only complaint with activities over fragments is that the activity comes from the bottom of the screen and (as far as I can tell) I can't make the drawer highlight + make the current activity unclickable/not destroy+create itself when clicked

w/e though I'm not about to spend three weeks figuring out UI bullshit when I have a fuckload of stuff I have to figure out on the backend

FAT32 SHAMER
Aug 16, 2012



Volmarias posted:

I have tried but simply cannot figure out what you mean by this.

I'll record it tomorrow

FAT32 SHAMER
Aug 16, 2012



baka kaba posted:

You just need to set up the menu for each activity, once the navigation drawer is initialised you can do getMenu() to poke around in there and find the item you want to change. You probably just want to look it up by id and then do setEnabled() to false. Usually you'd do something like setItemTextColor() to define what things should look like in states like disabled (or you can set that in XML)

Here's some people talking about old activity transitions - you can basically either set it on the activity when it starts and do some lifecycle janitoring, or do it app-wide by defining an animation in your app theme

Ahhhhh ok derp.

Here's a gif of what i was talking about
https://giant.gfycat.com/AnnualColorlessKakarikis.gifv

edit: ok the world of animations is fun and exciting and the normies are going to love these

FAT32 SHAMER fucked around with this message at 15:41 on Aug 23, 2017

FAT32 SHAMER
Aug 16, 2012



baka kaba posted:

Looks like I forgot to actually link the stack overflow page but sounds like you found them anyway!

Yeah, cheers :)

FAT32 SHAMER
Aug 16, 2012



John F Bennett posted:

Unrelated to things in this thread but what software do you use to create such a nice gif of your screen?

edit: oh never mind, you were filming your screen. I didn't notice it on my very cropped browser windows.

haha yeah, i built a new computer at work and havent installed all my software and didn't realize it until i went to record my screen

FAT32 SHAMER
Aug 16, 2012



Completely unrelated, is UnboundID the best LDAP lib for connecting my app to my company's LDAP server? I checked out something called Auth0 but they seem to be offering to host a new LDAP server instead of just giving me APIs to use to handle auth stuff

FAT32 SHAMER
Aug 16, 2012



Seriously tho if you're doing vehicle stuff then me and u are now Postin Pals

FAT32 SHAMER
Aug 16, 2012



Popete posted:

Using Android in embedded systems is all the rage these days, customers love having a slick interface that everyone already knows how to use to interface with there in-flight entertainment or seat controls. Android is nice but there are times where it is an ungodly pain in the rear end and I wish we would have just gone with a more traditional OS. Having to mess with HAL's and frameworks is a mess that I'm not very good at coming from a micro controller bare metal coding perspective.

Are you doing embedded Android stuff, my Postin Pal?

Nah the lowest level I got to was loving around with the HVAC control system and trying to figure out how it talked to the CAN/LIN buses lol

Based on my experience with OEMs building a full OS in house I'll put up with androids quirks any day

FAT32 SHAMER
Aug 16, 2012



b0lt posted:

Are you seriously shipping a froyo device in 2017?

android dot text


ok here's a question regarding views and dynamically adding textviews into them. I'm building a sidebar that will load in categories from a Firebase, and when an item is clicked it will show subitems, like this



(except without the radio buttons)

It has three columns: categoryName, completed, and score. When you click anywhere on that header it will turn grey to show that it is selected and show the subcategories and if they're completed or not. I was attempting to do this by hiding a view containing the subcategories and unhiding it with an onClick of the view like this

Java code:
sidebarViewGroupParent.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(!sidebarViewGroupChild.isShown()) {
                    sidebarViewGroupChild.setVisibility(View.VISIBLE);
                    sidebarViewGroupParent.setBackgroundColor(getResources().getColor(R.color.sidebarHighlight));
                } else {
                    sidebarViewGroupChild.setVisibility(View.GONE);
                    sidebarViewGroupParent.setBackgroundColor(getResources().getColor(R.color.sidebarColor));
                }
            }
        });
This may be reinventing the wheel since ExpandableLists exist, but I'm not sure that I am able to have 2 TextViews and an ImageView in the head of the list

I made a layout specifically for the categories here:
XML code:
<?xml version="1.0" encoding="utf-8"?>
<!-- CategoryParentView -->
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/qwedit_sidebar_group_parentview"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/qwedit_sidebar_item_parent"
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:textSize="15sp"
        android:textColor="@color/black"
        android:text="Logistics and Inventory Management"/>
    <ImageView
        android:id="@+id/qwedit_completed"
        android:layout_width="24dp"
        android:layout_height="24dp"
        android:src="@mipmap/checkmark"
        android:layout_centerVertical="true"
        android:layout_toEndOf="@+id/qwedit_sidebar_item_parent"
        android:layout_marginStart="23dp" />

    <TextView
        android:id="@+id/qwedit_sidebar_score"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="2dp"
        android:textSize="25sp"
        android:textColor="@color/colorPrimary"
        android:text="Score"
        android:layout_centerVertical="true"
        android:layout_alignParentEnd="true"
        android:layout_marginEnd="15dp" />
</RelativeLayout>
XML code:
<?xml version="1.0" encoding="utf-8"?>
<!-- CategoryChildView -->
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/qwedit_sidebar_group_childview"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    <TextView
        android:id="@+id/qwedit_sidebar_text_child"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="2dp"
        android:textSize="12sp"
        android:textColor="@color/black"
        android:text="child" />
    <ImageView
        android:id="@+id/qwedit_completed_child"
        android:layout_width="15dp"
        android:layout_height="15dp"
        android:src="@mipmap/checkmark"
        android:layout_centerVertical="true"
        android:layout_toEndOf="@+id/qwedit_sidebar_text_child"
        android:layout_marginStart="12dp" />
</RelativeLayout>
Am I able to insert these into the activity's layout by using a layoutinflater to add as many of these as there are categories pulled from the DB? The values hardcoded in the XML such as the source and the text values will be overwritten when the appropriate values are pulled from the Firebase and are mostly for me to not get lost. Am I going about this completely wrong or do I have the right idea but the wrong implementation? Pointers would be great. I feel like I'm writing a lot more layout code than I need to be and that I probably haven't been abusing the include functionality like I should be and I'm just making a big mess :newlol:



EDIT: ok turns out an ELV will solve all of my problems and i'm a gigantic dummy

FAT32 SHAMER fucked around with this message at 20:16 on Sep 6, 2017

FAT32 SHAMER
Aug 16, 2012



I dont use a physical device for android, just the emulator. Do that, unless your PC is slow to begin with


also, can anyone explain why the hell this is happening when i click on an EditText that is inside an ExpandableListView?
https://giant.gfycat.com/GenuineUnluckyHyrax.gifv

To me it looks like the toolbar is extending over everything, but maybe it's a weird quirk that has a quick fix? :ohdear:

FAT32 SHAMER fucked around with this message at 21:13 on Sep 12, 2017

FAT32 SHAMER
Aug 16, 2012



kitten smoothie posted:

Tried looking in the Hierarchy Viewer for where the views went?

How would i look to see where the views went? Heres what it looks like (EditText is highlighted)

FAT32 SHAMER
Aug 16, 2012



Tunga posted:

It's not doing something weird related to the input type is it? On phones TextViews can do this weird fullscreen thing unless you set flagNoExtractUi on them. This shouldn't happen on a tablet though.

Hmm, I don't think so. I'll try the flagNoExtractUi thing and see what happens, but I would assume that if they were expanding, the background would be the colour of the TextView instead of something else


edit: also for some reason when i click on the edit text then click out of it I can no longer click on one of the boxes (or at least, it makes the click sound but it doesnt change the color to highlighted)

Edit two: I think the issue is going to be resolved if I convert my activities to fragments, based on what I'm reading

Ugh

FAT32 SHAMER fucked around with this message at 19:23 on Sep 13, 2017

FAT32 SHAMER
Aug 16, 2012



You can still build activities and not have to worry about the device's screen size, i'm more concerned given the complexity of the app that i'm building vs the pain in the rear end ratio of rewriting my activities to fragments and debugging this weird edittext issue. I could rewrite it in fragments and still have the two bugs that i'm currently experiencing, which will be hard to justify as i slink farther behind

FAT32 SHAMER
Aug 16, 2012



kitten smoothie posted:

The view properties tab is helpful for looking at layout dimensions and such for a given view.

https://developer.android.com/studio/profile/hierarchy-viewer.html

Also I usually like to start at the target view and walk my way back up the hierarchy in the preview to see where things got wonky.
After digging through the parent views, I found several possible culprits.


This is the ActionBar before clicking on the EditText


After:


For some reason it's expanding the height to 996 even though I've set android:maxHeight="?attr/ActionBarSize"


Here is the first view that gets wonky


And to the left of the red line/arrow here is where it continues to be wonky, all the way to the root of all the views


I'm currently looking at my inflation methods in my BaseActivity that I wrote so I dont have to write so much boilerplate and other activities can inherit from, so fingers crossed I find the issue. I wonder if my CustomExpandableListAdapter inflaters are loving something up with the inflation of the ActionBar:thunk:

FAT32 SHAMER fucked around with this message at 13:06 on Sep 14, 2017

Adbot
ADBOT LOVES YOU

FAT32 SHAMER
Aug 16, 2012



TURNS OUT ALL I HAD TO DO WAS CHANGE THE MANIFEST TO adjustNothing THANK THE GODS

  • 1
  • 2
  • 3
  • 4
  • 5
  • Post
  • Reply