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



Tunga posted:

The last time someone did this to me I just quit my job, this will technically solve your problem but may not be the solution you were looking for.

Extremely tempting, however I have the ability to tell them "no you can't loving ship this because it's at best a beta"

The guy who moved up our timeline is probably getting canned and his boss is quitting at the end of the month, so hopefully this sorts itself out

Adbot
ADBOT LOVES YOU

LLSix
Jan 20, 2010

The real power behind countless overlords

LLSix posted:

I know this is a longshot, but does anyone have experience with building QT QML for android armv7? QT has issues with keeping their build tools up-to-date with the latest versions of Android but now I'm not even able to build with older versions of the android sdk and none of the ideas from the official forums have helped.

Here's the error I'm getting:

Trip Report: QT doesn't support the latest versions of the android SDK. Or the build tools from anytime this year. Here's the "solution" I settled on.

1. install QT android components via qt maintenance tool
2. install android sdk https://developer.android.com/studio/index.html
3. install android ndk https://developer.android.com/ndk/guides/index.html#download-ndk
a. version 10e due to QT not supporting more recent versions
4. install java jdk http://www.oracle.com/technetwork/java/javase/downloads/index.html
a. java JDK 8, android doesn't support 9 yet
5. install Gradle https://gradle.org/releases/
a. on ios > brew install gradle
6. download old android tools because QT doesn't fully support current versions of android
a. for windows https://dl.google.com/android/repository/tools_r25.2.5-windows.zip
b. for ios https://dl.google.com/android/repository/tools_r25.2.5-macosx.zip
c. copy downloaded tools over existing tools in <ndk_root_dir>\build\tools e.g. C:\android-ndk-r10e\build\tools


FAT32 SHAMER posted:

My dumbass PM told a customer that a six month app would be done in 4.5 when 6 months was best case, which means i need to have a working demo by december 8th lol
Last time this happened to me, I told my boss and the next day I had 2 more devs helping out. I miss that boss. Your mileage may very.

Dilbert solution: your demo is now a PP presentation of the mockup graphics provided by UX. If there is no UX team, than just use whatever graphics the PM used in his sales pitch.

Edit: nevermind, your solution is even better.

LLSix fucked around with this message at 21:41 on Nov 20, 2017

Splinter
Jul 4, 2003
Cowabunga!
Ever since upgrading to Android Studio 3 I haven't been able to get the Emulator working. The emulator doesn't get passed a black screen (never even makes it to a splash screen), though qemu is still responsive (can click the control bar and open up the extended controls window). Android Studio spits out some non descriptive emulator related messages in the Event Log before eventually beach balling and becoming unresponsive (I have to force quit). Any ideas?

The emulators worked fine prior to upgrading from 2.x to 3. I've tried creating new virtual devices, downloading/redownloading device images, creating new projects.

Here's a screen grab of the event log:

kitten smoothie
Dec 29, 2001

Splinter posted:

Ever since upgrading to Android Studio 3 I haven't been able to get the Emulator working. The emulator doesn't get passed a black screen (never even makes it to a splash screen), though qemu is still responsive (can click the control bar and open up the extended controls window). Android Studio spits out some non descriptive emulator related messages in the Event Log before eventually beach balling and becoming unresponsive (I have to force quit). Any ideas?

The emulators worked fine prior to upgrading from 2.x to 3. I've tried creating new virtual devices, downloading/redownloading device images, creating new projects.

Here's a screen grab of the event log:



What OS are you running? If Mac or Windows I'd double-check you're running the latest HAXM drivers.

Splinter
Jul 4, 2003
Cowabunga!
OSX. As best I can tell I have the latest HAXM drivers installed (6.2.1). Reinstalled for good measure.

I ran the emulator from the command line and logged the output. It spams "VCPU shutdown request"s. I re-ran with HAXM disabled (-accel off) and the same AVD booted (slowly...) with no shutdown requests, so it seems likely this is a HAXM issue.

Looks like Kaby Lake i7 users were reporting a similar issue with HAXM 6.0.4, which was supposedly fixed in 6.0.5. I'm wondering if a recent HAXM update introduced this same issue on my old processor (I'm running on a Sandy Bridge mobile i7). I'm pretty sure I updated HAXM (and other packages) at the same time I upgraded to Android Studio 3, so the Android Studio update may be unrelated to the issue.

Any thoughts on how to proceed?

Volmarias
Dec 31, 2002

EMAIL... THE INTERNET... SEARCH ENGINES...
The upgrade to AS 3.0 was almost certainly unrelated. The emulator is part of the SDK tools that get updated separately.

No good suggestions on how to fix this, if you can figure out a way to install a prior version of the SDK tools that's your best bet.

kitten smoothie
Dec 29, 2001

I forgot that they recently changed up the emulator (and which hypervisor it uses) on OS X. Maybe you took this emulator update at the same time you bumped to AS 3.0.

https://developer.android.com/studio/releases/emulator.html

quote:

Hypervisor.framework is now enabled by default on macOS for 32-bit x86 images to improve performance and macOS compatibility. If you experience issues with it specifically, please file a bug report and append HVF = off to ~/.android/advancedFeatures.ini (create this file if it doesn't exist).

So, option 1: try a 32-bit image, bypassing HAXM, and see if it still blows up. If not then use that.

Option 2: if you saw it already blowing up with a 32-bit image, turn off Hypervisor.framework using those instructions. (and be awesome and log a bug, leave it to google to care about whether it's a dupe or not).


edit: side note, if you had previously had trouble trying to run the emulator concurrently with Docker or Virtualbox on the same machine, this change to the emulator means you now can do so. This had been a long-standing gripe of mine that I couldn't run a local copy of the app's backend on the same machine as the emulator.

kitten smoothie fucked around with this message at 06:22 on Nov 24, 2017

FAT32 SHAMER
Aug 16, 2012



So, I'm working with an Expandable List View and when it inflates more than one view, i get some wonky behaviour that I havent been able to figure out

https://gfycat.com/gifs/detail/WarmLeftGreendarnerdragonfly

Here's what I would assume is the offending code:

Java code:
@Override
    public View getGroupView(int listPosition, boolean isExpanded,
                             View convertView, ViewGroup parent) {
        String listTitle = (String) getGroup(listPosition);
        if(convertView == null) {
            convertView = LayoutInflater.from(parent.getContext())
                    .inflate(R.layout.edit_main_elv_group, parent, false);
        }
        // TODO: add logic to pull question titles from firebase

        TextView listTitleTextView = (TextView) convertView
                .findViewById(R.id.edit_main_elv_parent_title);
        selectQuestionSwitch = (Switch) convertView.findViewById(R.id.select_question_button);
        selectQuestionSwitch.setFocusable(false);
        mainGroupContainer = convertView.findViewById(R.id.edit_main_elv_group_container);

        selectQuestionSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                if(isChecked) {
                    mainGroupContainer.setBackgroundColor(context.getResources()
                            .getColor(R.color.colorPrimary));
                    isQuestionBeingUsed = true;
                } else {
                    mainGroupContainer.setBackgroundColor(context.getResources()
                            .getColor(R.color.sidebarHighlight));
                isQuestionBeingUsed = false;
                }
            }
        });


        listTitleTextView.setText(listTitle);
        return convertView;
    }
And if you'd like the full code, here you go

Java code:

/**
 * Created by Tadhg on 9/8/2017.
 * Based on tutorial found here:
 * [url]http://www.journaldev.com/9942/android-expandablelistview-example-tutorial[/url]
 */

public class MainCustomExpandableListAdapter extends BaseExpandableListAdapter {
    private Context context;
    private List<String> expandableListTitle;
    private HashMap<String, List<String>> expandableListDetail;

    // Values inits
    private int categoryScore;
    private int subCategoryScore;
    private boolean completedCategory;
    private boolean completedSubCategory;
    boolean isQuestionBeingUsed;

    // Firebase Inits



    // Camera inits
    private static String root = null;
    private static String imageFolderPath = null;
    private String imageName = null;
    public static Uri fileUri = null;

    // ChildView views
    private View badScoreView;
    private View mediocreScoreView;
    private View goodScoreView;
    private View worldclassScoreView;
    private TextView badScoreTextView;
    private TextView mediocreScoreTextView;
    private TextView goodScoreTextView;
    private TextView worldClassScoreTextView;
    private EditText questionCommentEditText;
    private ImageView addPhotoButton;
    private View childContainer;
    private ImageView imageThumbnail1;

    // MainView views
    private Switch selectQuestionSwitch;
    private View mainGroupContainer;

    public MainCustomExpandableListAdapter(Context context, List<String> expandableListTitle,
                                           HashMap<String, List<String>> expandableListDetail) {
        this.context = context;
        this.expandableListTitle = expandableListTitle;
        this.expandableListDetail = expandableListDetail;
    }

    @Override
    public Object getChild(int listPosition, int expandedListPosition) {
        return this.expandableListDetail
                .get(this.expandableListTitle.get(listPosition))
                .get(expandedListPosition);
    }

    @Override
    public long getChildId(int listPosition, int expandedListPosition) {
        return expandedListPosition;
    }

    @Override
    public View getChildView(int listPosition, final int expandedListPosition, boolean isLastChild,
                             View convertView, ViewGroup parent) {
        final String expandedListText = (String) getChild(listPosition, expandedListPosition);

        if(convertView == null) {
            LayoutInflater layoutInflater = (LayoutInflater) this.context
                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = layoutInflater.inflate(R.layout.edit_main_elv_item, null);
        }
        // ChildView views
        badScoreView = convertView.findViewById(R.id.edit_main_badView);
        mediocreScoreView = convertView.findViewById(R.id.edit_main_mediocreView);
        goodScoreView = convertView.findViewById(R.id.edit_main_goodView);
        worldclassScoreView = convertView.findViewById(R.id.edit_main_wcView);
        badScoreTextView = (TextView) convertView.findViewById(R.id.edit_main_badTextView);
        mediocreScoreTextView = (TextView) convertView.findViewById(R.id.edit_main_mediumTextView);
        goodScoreTextView = (TextView) convertView.findViewById(R.id.edit_main_goodTextView);
        worldClassScoreTextView = (TextView) convertView.findViewById(R.id.edit_main_worldclassTextView);
        childContainer = convertView.findViewById(R.id.edit_main_child_container);

        questionCommentEditText = (EditText) convertView.findViewById(R.id.edit_main_commentEditText);
        addPhotoButton = (ImageView) convertView.findViewById(R.id.edit_add_photo_button);

        // TODO: add logic to set ScoreView texts to answers dynamically
        //
        //
        //


        badScoreView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // TODO: add logic to change score
                subCategoryScore = 1;
                badScoreView.setBackgroundColor(context.getResources()
                        .getColor(R.color.badScoreSelected));
                mediocreScoreView.setBackgroundColor(context.getResources()
                        .getColor(R.color.mediocreScore));
                goodScoreView.setBackgroundColor(context.getResources()
                        .getColor(R.color.goodScore));
                worldclassScoreView.setBackgroundColor(context.getResources()
                        .getColor(R.color.worldclassScore));
            }
        });

        mediocreScoreView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // TODO: add logic to change score
                subCategoryScore = 2;
                badScoreView.setBackgroundColor(context.getResources()
                        .getColor(R.color.badScore));
                mediocreScoreView.setBackgroundColor(context.getResources()
                        .getColor(R.color.mediocreScoreSelected));
                goodScoreView.setBackgroundColor(context.getResources()
                        .getColor(R.color.goodScore));
                worldclassScoreView.setBackgroundColor(context.getResources()
                        .getColor(R.color.worldclassScore));
            }
        });

        goodScoreView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // TODO: add logic to change score
                subCategoryScore = 3;
                badScoreView.setBackgroundColor(context.getResources()
                        .getColor(R.color.badScore));
                mediocreScoreView.setBackgroundColor(context.getResources()
                        .getColor(R.color.mediocreScore));
                goodScoreView.setBackgroundColor(context.getResources()
                        .getColor(R.color.goodScoreSelected));
                worldclassScoreView.setBackgroundColor(context.getResources()
                        .getColor(R.color.worldclassScore));
            }
        });

        worldclassScoreView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // TODO: add logic to change score
                subCategoryScore = 4;
                badScoreView.setBackgroundColor(context.getResources()
                        .getColor(R.color.badScore));
                mediocreScoreView.setBackgroundColor(context.getResources()
                        .getColor(R.color.mediocreScore));
                goodScoreView.setBackgroundColor(context.getResources()
                        .getColor(R.color.goodScore));
                worldclassScoreView.setBackgroundColor(context.getResources()
                        .getColor(R.color.worldClassScoreSelected));
            }
        });
        /**
         * Use variables categoryScore and subCategoryScore to update scores
         */


        questionCommentEditText.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                questionCommentEditText.setFocusable(true);
                questionCommentEditText.setCursorVisible(true);
            }
        });



        questionCommentEditText.setOnKeyListener(new View.OnKeyListener() {
            @Override
            public boolean onKey(View v, int keyCode, KeyEvent event) {
                if(keyCode == 66) {
                    InputMethodManager manager = (InputMethodManager) v.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
                    if(manager != null) {
                        manager.hideSoftInputFromWindow(v.getWindowToken(), 0);
                        questionCommentEditText.clearFocus();
                    }
                }
                return false;
            }
        });

        addPhotoButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // TODO: add logic to bring up camera and save picture to db
                captureImage(v);

            }
        });
        return convertView;
    }

    public void captureImage(View v) {


        Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        ((Activity) context).startActivityForResult(cameraIntent, EditDraftActivity.REQUEST_CAMERA_ACCESS);
    }


    @Override
    public int getChildrenCount(int listPosition) {
        return this.expandableListDetail.get(this.expandableListTitle.get(listPosition)).size();
    }

    @Override
    public Object getGroup(int listPosition) {
        return this.expandableListTitle.get(listPosition);
    }

    @Override
    public int getGroupCount() {
        return this.expandableListTitle.size();
    }

    @Override
    public long getGroupId(int listPosition) {
        return listPosition;
    }


    @Override
    public View getGroupView(int listPosition, boolean isExpanded,
                             View convertView, ViewGroup parent) {
        String listTitle = (String) getGroup(listPosition);
        if(convertView == null) {
            convertView = LayoutInflater.from(parent.getContext())
                    .inflate(R.layout.edit_main_elv_group, parent, false);
        }
        // TODO: add logic to pull question titles from firebase

        TextView listTitleTextView = (TextView) convertView
                .findViewById(R.id.edit_main_elv_parent_title);
        selectQuestionSwitch = (Switch) convertView.findViewById(R.id.select_question_button);
        selectQuestionSwitch.setFocusable(false);
        mainGroupContainer = convertView.findViewById(R.id.edit_main_elv_group_container);

        selectQuestionSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                if(isChecked) {
                    mainGroupContainer.setBackgroundColor(context.getResources()
                            .getColor(R.color.colorPrimary));
                    isQuestionBeingUsed = true;
                } else {
                    mainGroupContainer.setBackgroundColor(context.getResources()
                            .getColor(R.color.sidebarHighlight));
                isQuestionBeingUsed = false;
                }
            }
        });


        listTitleTextView.setText(listTitle);
        return convertView;
    }

    @Override
    public boolean hasStableIds() { return false; }

    @Override
    public boolean isChildSelectable(int listPosition, int expandedListPosition) { return true; }
}

// TODO: Camera stuff [url]https://www.youtube.com/watch?v=Zy2DKo0v-OY[/url]

I'm not sure if my ELV should be doing all the heavy lifting that is involved with onClickListeners and all that jazz too, but right now my main concern is the wonky behaviour in the gif i made.

edit: I'm guessing that it is a problem with the inflation method, possibly the same variable or UI widget with the same ID triggering each other? I have no idea how i would set listeners for a switch or an ELV group that needs an ID to be able to do what it needs to do and look the way i need it to look since this is obviously pretty complicated to be stuffed inside an ELV.

I'm currently attempting to convert the ELV child that contains all that poo poo to a CardView with a RecyclerView instead to hopefully not have the same issue

FAT32 SHAMER fucked around with this message at 22:08 on Nov 28, 2017

Volmarias
Dec 31, 2002

EMAIL... THE INTERNET... SEARCH ENGINES...
I don't know why you're making those views member variables, but that's your problem. Your listeners for each row reference the exact same view of the last row that assigned them.

FAT32 SHAMER
Aug 16, 2012



Volmarias posted:

I don't know why you're making those views member variables, but that's your problem. Your listeners for each row reference the exact same view of the last row that assigned them.

:doh:

I did that to avoid having to declare views final, i didnt realize that it would cause those types of bugs. Thanks so much!

Telephones
Apr 28, 2013
e I'm sorry, let me do more reading first.

Telephones fucked around with this message at 22:04 on Nov 29, 2017

Splinter
Jul 4, 2003
Cowabunga!
If I want to support Android O NotificationChannel stuff on an app that needs to be backwards compatible, do I need to wrap all NotificationChannel related code in something like if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)? I don't have a pre-O device handy for testing at the moment and I'm a bit of an Android newb.

e: this is what I'm working with
code:
    private void setupNotifications() {
        mNotifyMgr = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
            mChannel = new NotificationChannel(
                    NOTIFICATION_CHANNEL_ID,
                    NOTIFICATION_CHANNEL_NAME,
                    NotificationManager.IMPORTANCE_MIN);

            mChannel.enableVibration(false);

            mNotifyMgr.createNotificationChannel(mChannel);
        }

        // Intent for using the next action from the notification
        Intent nextIntent = new Intent(KEY_NEXT);
        PendingIntent nextPendingIntent = PendingIntent.getBroadcast(this, 0, nextIntent, 0);

        // Intent for clicking the notification
        Intent resultIntent = new Intent(this, MainActivity.class);
        PendingIntent resultPendingIntent =
                PendingIntent.getActivity(
                        this,
                        0,
                        resultIntent,
                        PendingIntent.FLAG_UPDATE_CURRENT
                );

        mBuilder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
                .setSmallIcon(R.drawable.ic_clock)
                .setContentTitle(mCurrentTime.getTime())
                .setContentText(mCurrentTime.getNextCriteria())
                .addAction(android.R.drawable.ic_media_play, "Yes", nextPendingIntent)
                .setPriority(NotificationCompat.PRIORITY_MIN)
                .setCategory(Notification.CATEGORY_STATUS)
                .setContentIntent(resultPendingIntent);
    }

Splinter fucked around with this message at 23:38 on Nov 30, 2017

Volguus
Mar 3, 2009
Set it up in an emulator. You can have any and all needed environments.

Splinter
Jul 4, 2003
Cowabunga!
Alas, I have not yet resolved my emulator issue.

Volmarias
Dec 31, 2002

EMAIL... THE INTERNET... SEARCH ENGINES...
You have it right. You generally don't need to keep the channel reference around either.

FAT32 SHAMER
Aug 16, 2012



Is there a reason why getting a goddamn image URI from the camera is such a pain in the dick? I've followed Google's Taking Photos Simply guide to the T and i still can only get a really lovely bitmap and a null uri :(

All other resources on the topic seem to be out of date due to the FileProvider update and all that jazz too. This shouldn't be as annoying to retrieve as it is.

edit: I guess the fact that I'm trying to do this between an ExpandableListViewAdapter and the activity could be hampering it.

FAT32 SHAMER fucked around with this message at 00:16 on Dec 12, 2017

Volmarias
Dec 31, 2002

EMAIL... THE INTERNET... SEARCH ENGINES...
This indeed should not be a terrible thing, feel free to paste your terrible code.

FAT32 SHAMER
Aug 16, 2012



Volmarias posted:

This indeed should not be a terrible thing, feel free to paste your terrible code.

I think that I found a solution, based on this:

https://stackoverflow.com/questions/35243560/onactivityresult-inside-a-recyclerview-adapter-not-being-used

I’ll post my code tomorrow if I don’t get it sorted. I’m calling the ((Activity) context).startActivityForResult(); as the asker in that question is and suddenly I realize why it’s not working correctly haha

edit: actually im not sure. Here is my code in my activity:

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

        if(requestCode == REQUEST_CAMERA_ACCESS && resultCode == RESULT_OK) {

            Bundle extras = data.getExtras();
            Bitmap imageBitmap = (Bitmap) extras.get("data");

            String questionKey = (String) extras.get("question_key");

            // dynamic image
            LinearLayout mPhotoScroller = (LinearLayout) findViewById(R.id.photo_scrollview);
            ImageView imageView = new ImageView(this);
            imageView.setAdjustViewBounds(true);
            imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
            imageView.setImageBitmap(imageBitmap);
            LinearLayout.LayoutParams mLayoutParams = new LinearLayout.LayoutParams(
                    LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
            mLayoutParams.setMargins(5,5,5,5);
            imageView.setLayoutParams(mLayoutParams);
            mPhotoScroller.addView(imageView);

            Uri tempUri = getImageUri(getApplicationContext(), imageBitmap);
            File finalFile = new File(getRealPathFromUri(tempUri));

            String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
            StorageReference storagePath = mStorage.child(mAuth.getCurrentUser().getUid()).child(assessmentKey).child("-llkdsfj;alksdjf").child(timeStamp); //test key until i pull the question's actual key 
            UploadTask uploadTask = storagePath.putFile(tempUri);
            mProgress.setMessage("Uploading Image...");
            mProgress.show();
            uploadTask.addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() {
                @Override
                public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
                    Uri downloadUrl = taskSnapshot.getDownloadUrl();
                    mProgress.dismiss();
                    Toast.makeText(EditDraftActivity.this, "Uploaded successfully", Toast.LENGTH_SHORT).show();
                }
            }).addOnFailureListener(new OnFailureListener() {
                @Override
                public void onFailure(@NonNull Exception e) {
                    mProgress.dismiss();
                    Toast.makeText(EditDraftActivity.this, "Upload failed. Will upload when internet connectivity is established.", Toast.LENGTH_SHORT).show();
                }
            });
        }
    }

    public Uri getImageUri(Context inContext, Bitmap inImage) {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        inImage.compress(Bitmap.CompressFormat.JPEG, 100, byteArrayOutputStream);
        //byte[] byteArray = byteArrayOutputStream.toByteArray();
        String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
        String imageFileName ="JPEG_" + timeStamp + "_";
        String path = MediaStore.Images.Media.insertImage(inContext.getContentResolver(), inImage, imageFileName, null);
        return Uri.parse(path);
    }

    public String getRealPathFromUri(Uri uri) {
        Cursor cursor = getContentResolver().query(uri, null, null, null, null);
        cursor.moveToFirst();
        int i = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
        return cursor.getString(i);
    }


    public void captureImage(View v, String questionKey) {
        Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        if(cameraIntent.resolveActivity(v.getContext().getPackageManager()) != null) {
            ((Activity) v.getContext()).startActivityForResult(cameraIntent,
                    REQUEST_CAMERA_ACCESS);
        }
    }
and here is where I call it in my adapter (that spawns however many camera buttons as there are children for a given view in my expandableListView

Java code:
addPhotoButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mEditDraftActivity.captureImage(v, "-Llkjsa;flkja;lkdsj"/*questionsReference.getKey()*/);
            }
With this, I currently am able to upload the bitmap returned from the getExtras and display it as a thumbnail, but it is a tiny and lovely image and I havent been able to figure out how to 1)upload the high quality version of the bitmap to firebase storage 2) have that image be able to be clicked on and show the large version from inside the app. I can probably do this by using an onClickListener on the dynamically generated imageviews, but i'm not sure. So far the rest of the app has worked out great and the users loved the beta build i showed them on friday, and while this photo feature was optional according to spec, I feel like im super close to having it right and would like to be able to ship it working properly on Wednesday.


The code that I currently have came from a different tutorial after Google's didnt do anything for me (for whatever reason)

FAT32 SHAMER fucked around with this message at 02:46 on Dec 12, 2017

kitten smoothie
Dec 29, 2001

Your garbage bitmap is because you need to create a File for the photo yourself, then pass that along in the image capture intent. Otherwise you're just getting a thumbnail image.

Keep reading the Android developer doc you linked, starting with "Save the Full-Size Photo."

quote:

The Android Camera application saves a full-size photo if you give it a file to save into. You must provide a fully qualified file name where the camera app should save the photo.

You need to create that file and give a uri to it as an extra to the image capture intent. There's example code in that doc for dealing with this.

Once you have a File reference, you can just use Firebase's putFile to upload it.

FAT32 SHAMER
Aug 16, 2012



kitten smoothie posted:

Your garbage bitmap is because you need to create a File for the photo yourself, then pass that along in the image capture intent. Otherwise you're just getting a thumbnail image.

Keep reading the Android developer doc you linked, starting with "Save the Full-Size Photo."


You need to create that file and give a uri to it as an extra to the image capture intent. There's example code in that doc for dealing with this.

Once you have a File reference, you can just use Firebase's putFile to upload it.

I’m not really sure how I didn’t catch that’s what mCurrentPhotoPath would be used for but I’m glad to see I’m at least getting progressively less dumb :doh:

Splinter
Jul 4, 2003
Cowabunga!
When using a bound service is it normal to use a boolean member field to track whether the service is bound (e.g. set in onServiceConnected/Disconnected and after calling unbindService) and then wrap any service function calls in if (mBound) checks? If for some reason the service gets unexpectedly disconnected, what's the best way to recover when the user is expecting an immediate response to their action? I don't think calling bindService again inside the else and then proceeding with the service function call inline is an option because I believe bindService is async and therefore the service member will still almost certainly not be rebound in time for the subsequent function call. I'm thinking of just calling bindService and toasting a "try again" message, but I want to make sure there's not a more elegant best practice I'm missing.

Volmarias
Dec 31, 2002

EMAIL... THE INTERNET... SEARCH ENGINES...

Splinter posted:

When using a bound service is it normal to use a boolean member field to track whether the service is bound (e.g. set in onServiceConnected/Disconnected and after calling unbindService) and then wrap any service function calls in if (mBound) checks? If for some reason the service gets unexpectedly disconnected, what's the best way to recover when the user is expecting an immediate response to their action? I don't think calling bindService again inside the else and then proceeding with the service function call inline is an option because I believe bindService is async and therefore the service member will still almost certainly not be rebound in time for the subsequent function call. I'm thinking of just calling bindService and toasting a "try again" message, but I want to make sure there's not a more elegant best practice I'm missing.

Is your service local or remote? If it's your own service in your own process, my understanding is that you should never have to worry about it going away, and even a remote service you've bound to disappearing out from underneath of you while the app is foregrounded is almost impossible unless the device has severe pressure.

If there's any concern about your calls possibly failing, you pretty much have no choice but to either have an async-oriented design, or have a wrapper class that fakes it by blocking as appropriate (and specify that you can't make the call on the UI thread, etc).

Take a look at https://developer.android.com/reference/android/app/Service.html#ProcessLifecycle

All that said, you shouldn't need to have a boolean to keep track of the bound state, you should have a binder object that's either present or null.

Splinter
Jul 4, 2003
Cowabunga!
It's a local service running in the same process on the UI thread, so yeah, I wouldn't expect it to be killed while the Activity is in the foreground. I don't think there should be a concern with the calls failing, but both the dev guide and another tutorial I went through use the "if (mBound)" approach for a similar situation (same process & thread). That's what got me thinking maybe I should be concerned. The sample in your link is using a boolean to track as well (though it doesn't show any actual service method calls from the activity).

With this approach you keep a reference to the service rather than the binder. I think the issue is if the service unexpectedly disconnects after binding, the service reference won't be null but method calls will still fail. I suppose the service could be set to null after calling unbindService() and in onServiceDisconnect() rather than tracking with a boolean, but I guess the boolean approach makes for cleaner code than checking for a null object everywhere.

Splinter fucked around with this message at 22:25 on Dec 15, 2017

Volmarias
Dec 31, 2002

EMAIL... THE INTERNET... SEARCH ENGINES...

Splinter posted:

It's a local service running in the same process on the UI thread, so yeah, I wouldn't expect it to be killed while the Activity is in the foreground. I don't think there should be a concern with the calls failing, but both the dev guide and another tutorial I went through use the "if (mBound)" approach for a similar situation (same process & thread). That's what got me thinking maybe I should be concerned. The sample in your link is using a boolean to track as well (though it doesn't show any actual service method calls from the activity).

With this approach you keep a reference to the service rather than the binder. I think the issue is if the service unexpectedly disconnects after binding, the service reference won't be null but method calls will still fail. I suppose the service could be set to null after calling unbindService() and in onServiceDisconnect() rather than tracking with a boolean, but I guess the boolean approach makes for cleaner code than checking for a null object everywhere.

Yeah, my suggestion was that onServiceDisconnect etc would null out the service.

That said, what's your use case for having a bound local service, given the service changes in Android O?

Splinter
Jul 4, 2003
Cowabunga!
So I guess the conclusion is wrapping all the service method calls in logic (whether using a boolean or checking for null) shouldn't be necessary in this situation but the google examples still do this to be defensive.

I had functionality within an activity that I also needed in a notification action (save data to a shared pref and then update the notification). I originally tried using a broadcast receiver, but predictably the notification action became unresponsive if the app process was killed/destroyed. So I moved the notification and shared pref handling to a service. The activity binds to the service while it's in the foreground, and the notification action starts the service (which then stops itself after performing the action) when it's clicked. This seems to work fine, but is there another way I should be doing this? This is a really basic app: 1 action that can be performed from either the main activity or a notification, and a settings activity with 2 prefs.

Volmarias
Dec 31, 2002

EMAIL... THE INTERNET... SEARCH ENGINES...

Splinter posted:

So I guess the conclusion is wrapping all the service method calls in logic (whether using a boolean or checking for null) shouldn't be necessary in this situation but the google examples still do this to be defensive.


The examples were written in an earlier time, and are meant to illustrate good behaviour. Technically you should always check to see if the bound object is still there, but if a service is being killed out from under the user while they're interacting with it then the device is basically hosed anyway.

quote:

I had functionality within an activity that I also needed in a notification action (save data to a shared pref and then update the notification). I originally tried using a broadcast receiver, but predictably the notification action became unresponsive if the app process was killed/destroyed. So I moved the notification and shared pref handling to a service. The activity binds to the service while it's in the foreground, and the notification action starts the service (which then stops itself after performing the action) when it's clicked. This seems to work fine, but is there another way I should be doing this? This is a really basic app: 1 action that can be performed from either the main activity or a notification, and a settings activity with 2 prefs.

Edit: I misread your post. Modifying a shared preferences is well within the remit of what you can Just Do in a receiver. What are you doing that makes this non viable? Is reloading your data a long running action? Starting a service is probably reasonable if you need to do IO.

Worth mentioning, you might be able to move your data modification work into its own (static?) class and just invoke that directly, instead of worrying about binding to the service. Then, use something like IntentService for your notifications' target that also just invokes that class.

Volmarias fucked around with this message at 15:22 on Dec 16, 2017

Splinter
Jul 4, 2003
Cowabunga!
I should clarify, when I said the receiver approach predictably caused the notification to become unresponsive if the app was destroyed, that was when all the notification code was in the activity and the receiver was calling an activity function. At that point I ended up moving stuff to a service because a) I was more familiar with services and b) the code required minimal refactoring after moving to a service. Thinking about it now, I think your suggestion of a receiver or intent service handling the notification with the data modification moved to a separate class could work, though it wouldn't necessarily result in simpler code. Is the main reason for the suggestion just to avoid the binding code in the activity, or is there another downside to my current service approach?

Volguus
Mar 3, 2009
So my company hired another company (5 people or so) to test our up and coming thing. It consists of many moving parts, including an Android app. Now, in that Android app we have a "Save" feature and therefore we have a save menu with a floppy disk icon. In the report that we got back I saw: "The floppy icon is old school, just use the word Save". Ughh ... what? Is that so? When did that happen?

LLSix
Jan 20, 2010

The real power behind countless overlords

Volguus posted:

So my company hired another company (5 people or so) to test our up and coming thing. It consists of many moving parts, including an Android app. Now, in that Android app we have a "Save" feature and therefore we have a save menu with a floppy disk icon. In the report that we got back I saw: "The floppy icon is old school, just use the word Save". Ughh ... what? Is that so? When did that happen?

Just pointless bike-shedding. It's not even good bike-shedding. Using icons in place of text only works well if everyone can immediately recognize the icon. The floppy disk icon is what everyone has always used for saving and that's why everyone keeps using it.

baka kaba
Jul 19, 2003

PLEASE ASK ME, THE SELF-PROFESSED NO #1 PAUL CATTERMOLE FAN IN THE SOMETHING AWFUL S-CLUB 7 MEGATHREAD, TO NAME A SINGLE SONG BY HIS EXCELLENT NU-METAL SIDE PROJECT, SKUA, AND IF I CAN'T PLEASE TELL ME TO
EAT SHIT

There's a save icon in Google's Material icon library, and you can guess what it is! But it might be more a case of when you use icons and when you don't - have a look at these full screen dialogs - the "confirm" option is meant to be text

If you want an idea of the modern look for apps, those design guidelines are the place to go (some of it's scattered across several pages though, like if you want to know what padding to use)

Glimm
Jul 27, 2005

Time is only gonna pass you by

Volguus posted:

So my company hired another company (5 people or so) to test our up and coming thing. It consists of many moving parts, including an Android app. Now, in that Android app we have a "Save" feature and therefore we have a save menu with a floppy disk icon. In the report that we got back I saw: "The floppy icon is old school, just use the word Save". Ughh ... what? Is that so? When did that happen?

This sounds like an Apple HIG suggestion and they're just applying it to Android where it doesn't make sense as a strict rule.

Volguus
Mar 3, 2009

Glimm posted:

This sounds like an Apple HIG suggestion and they're just applying it to Android where it doesn't make sense as a strict rule.

Wait, so apple does that on iOS?

FAT32 SHAMER
Aug 16, 2012



Volguus posted:

Wait, so apple does that on iOS?

Yeah, most Apple apps dont use an icon to say save, they just use text since so many children have no idea what a floppy disk is

Volguus
Mar 3, 2009

baka kaba posted:

There's a save icon in Google's Material icon library, and you can guess what it is! But it might be more a case of when you use icons and when you don't - have a look at these full screen dialogs - the "confirm" option is meant to be text

If you want an idea of the modern look for apps, those design guidelines are the place to go (some of it's scattered across several pages though, like if you want to know what padding to use)

That's a really confusing guideline. The New Event screen has a Save in the menu bar, but the Inbox screen has a Search and Confirm (or whatever that is) icons. So, Search is ok to have an icon, but Save isn't? What are these people smoking?

Plorkyeran
Mar 22, 2007

To Escape The Shackles Of The Old Forums, We Must Reject The Tribal Negativity He Endorsed

FAT32 SHAMER posted:

Yeah, most Apple apps dont use an icon to say save, they just use text since so many children have no idea what a floppy disk is

Apple apps (and third-party apps following the HiG) don't have a save button at all.

Taffer
Oct 15, 2010


Volguus posted:

So my company hired another company (5 people or so) to test our up and coming thing. It consists of many moving parts, including an Android app. Now, in that Android app we have a "Save" feature and therefore we have a save menu with a floppy disk icon. In the report that we got back I saw: "The floppy icon is old school, just use the word Save". Ughh ... what? Is that so? When did that happen?

It's a style choice. Text is increasingly common for saving, since "save" is a less common operation on mobile. Usually you're on the cloud and things save automatically, and instead of save you have a download button.

The save icon isn't wrong, per se, but its no longer ubiquitous and text is probably the better choice.

baka kaba
Jul 19, 2003

PLEASE ASK ME, THE SELF-PROFESSED NO #1 PAUL CATTERMOLE FAN IN THE SOMETHING AWFUL S-CLUB 7 MEGATHREAD, TO NAME A SINGLE SONG BY HIS EXCELLENT NU-METAL SIDE PROJECT, SKUA, AND IF I CAN'T PLEASE TELL ME TO
EAT SHIT

Volguus posted:

That's a really confusing guideline. The New Event screen has a Save in the menu bar, but the Inbox screen has a Search and Confirm (or whatever that is) icons. So, Search is ok to have an icon, but Save isn't? What are these people smoking?

You need to read the whole page (and others) for the reasoning

Those icons in the background are a menu on the main toolbar, menus are meant to be shown like that (and any that end up in the overflow menu can have the label displayed, since you can't press them for a tooltip hint)

Simple dialogs are meant to have some stuff on a little popup, and then a text button or two at the bottom - generally an "OK" and a "cancel", like an alert popup

Full-screen dialogs are meant to be a more complicated version, where the small dialog can't really fit in all the stuff you want to include (on mobile at least). So you're meant to remake it as a full-screen layout, with the OK button moved up to the right of the titlebar (still as text), and the Cancel represented as an X, where the back arrow usually goes on a normal full-screen activity

Volguus
Mar 3, 2009

baka kaba posted:

You need to read the whole page (and others) for the reasoning

Those icons in the background are a menu on the main toolbar, menus are meant to be shown like that (and any that end up in the overflow menu can have the label displayed, since you can't press them for a tooltip hint)

Simple dialogs are meant to have some stuff on a little popup, and then a text button or two at the bottom - generally an "OK" and a "cancel", like an alert popup

Full-screen dialogs are meant to be a more complicated version, where the small dialog can't really fit in all the stuff you want to include (on mobile at least). So you're meant to remake it as a full-screen layout, with the OK button moved up to the right of the titlebar (still as text), and the Cancel represented as an X, where the back arrow usually goes on a normal full-screen activity

Right, so an icon for the Save (and not text) is what should be done then when it is on a main menu. Though I agree with the theory that full screen dialogs should get dialog buttons (with text), in practice, even in their page, it simply looks out of place to have one thing in one place (icons, sure main menu but the user doesn't know or care what's main menu or secondary one) and text in another. I know I saw it used in google android apps and it always looked out of place and inconsistent with the rest of the app. But i guess once people get used to it it'll become normal just like the floppy icon did even though it was obsolete when hard-drives appeared and one no longer saved to the floppy.

Vesi
Jan 12, 2005

pikachu looking at?
make sure a "Save" button is actually necessary too, it's a bit of an old paradigm

Adbot
ADBOT LOVES YOU

Volguus
Mar 3, 2009

Vesi posted:

make sure a "Save" button is actually necessary too, it's a bit of an old paradigm

Well, i don't know. The users would use our app to create a thing that then it would be saved into their account online (our web application). That thing needs a name before it can be saved, so the save button pops up a dialog asking for the name and off it goes. Now, one could say that this operation can be called Upload and they'd be kinda right, but to be fair I'm not so sure that Upload is better than Save. At the end of the day is about semantics. The app (the entire solution in general) is targeted at developers not average joe six-pack, so they should most likely know what is going on.

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