SimpleSQL使ってるときの最新のDBファイルどこにあるのか問題(Mac編)

 SimpleSQLにおいてDBは「*.bytes」というファイルがDBのデータらしい。なんというか、このファイルにいろんなテーブルデータが入ってるみたい。

 このDBデータ(以下、test.bytesと呼ぶ)test.bytesは、最初は「DB Browser for SQLite」ソフトを使って作ることになる。適当にテーブル作ってtest.bytesファイルを出力、それをUnity上で使用することになる。

 ただ、Unity上に置いたtest.bytesファイルが常に更新されていくわけではないので注意。アプリ内でテーブルに対してデータのinsertやupdateなんかをしたとしても、Unity上のtest.bytesファイルは新しくならん。これを読むのは最初の最初だけのようだ。

 じゃあ、更新しまくられている最新のファイルはどこにあるのかというと、自分のMacだと以下の場所にあった。

 場所はここ(たぶんユーザーごと、それぞれで変わるべ)

Macintosh HD⁩ ▸ ⁨ユーザ⁩ ▸ ⁨<なんか自分の名前> ▸ ⁨ライブラリ⁩ ▸ ⁨Application Support⁩ ▸ ⁨DefaultCompany⁩ ▸ <なんかアプリの名前> ▸ test.bytes

 ここにおいてある「test.bytes」ファイルを「DB Browser for SQLite」とかのDB管理ソフトで読み込むと最新のテーブル情報が見れます。これを更新表示しつつ、アプリを作っていくといろいろ便利でよか。

 ちなみにアプリキャッシュクリアとかすると、ここのファイル消える。そんでまたゼロから新たに始めるため、Unity上においた「test.bytes」を読込する。こわーい。クリアこわーい。

 つまり、ユーザーがアプリキャッシュクリアしたら消えるというわけよ。

UnityでSQLite使うときはもう、アセットのSimpleSQL買っとけ

 無料のアセットとかあるけどさぁ、いろいろやったけどさぁ。SimpleSQLいちばん。


  4000円出して、SimpleSQL買っとけ。便利だし安定してるし4000円以上の価値ある。
 1から組むとかやめとけ、時間もったいねぇ。SQLマニアというのなら止めん。

SimpleSQL
https://assetstore.unity.com/packages/tools/integration/simplesql-38

SimpleSQL

UnityでSQLを利用するときはDB Browser for SQLite使っとけ

 UnityでSQLを利用したい。ちょっと大きめのプログラムになるとデータを管理するSQLをすぐ導入したほうがいい。

 最初はめんどくさいけど、やっぱりSQL入れた方がいい。SQLiteのことね。サーバーで動作する奴じゃなくて、アプリ内のみで動くスタンドアロン的なSQLね。SQLiteね。その管理DB用のPGはいろいろ使って見たんだけど以下が一番良かった。

DB Browser for SQLite
https://sqlitebrowser.org/

DB Browser for SQLite

シンプルだし使いやすかった。

GridView、グリッドビューに入れた画像が綺麗にならない、バラバラになる

みんな大好きグリッドビュー。

// 入れ込むべきレイアウト
GridView gridView = (GridView) v.findViewById(R.id.gridView);
gridView.setAdapter(new ImageAdapter(context, imageUrlList));

こうやって使いまっさ。ImageAdapterは独自のもの。そこに、context imageUrlListを渡します。

しかし、GridView、文字をいれるだけだと楽勝であるのだが、ネットから画像をよみこんでいれるとなるときっついのだ。それぞれの大きさがバラバラになったりして泣きそうになるのだ。他のサイトではすごく簡単にやり遂げているが。

import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.support.v4.util.LruCache;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;

import com.android.volley.RequestQueue;
import com.android.volley.toolbox.ImageLoader;
import com.android.volley.toolbox.Volley;

import java.util.List;

/**
 * イメージローダー
 */
public class ImageAdapter extends ArrayAdapter<String> {

	public final String TAG = "ImageAdapter";

	private Context context;
	private RequestQueue mQueue;
	private ImageLoader mImageLoader;

	public ImageAdapter(Context context, List<String> objects) {
		super(context, 0, objects);
		this.context = context;
		mQueue = Volley.newRequestQueue(getContext());
		mImageLoader = new ImageLoader(mQueue, new BitmapCache());
	}

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		String url = getItem(position);

		ImageView imageView;

		if (convertView == null) {
			imageView = new ImageView(getContext());

		} else {
			imageView = (ImageView) convertView;
		}
		// 画像取得処理
		ImageLoader.ImageListener listener = ImageLoader.getImageListener(imageView, android.R.drawable.ic_menu_rotate, android.R.drawable.ic_delete);

		// BASIC認証
		mImageLoader = new BasicAuthImageLoader(mQueue, new ImageLoader.ImageCache() {
			private final LruCache<String, Bitmap> mCache = new LruCache<String, Bitmap>(100);

			public void putBitmap(String url, Bitmap bitmap) {
				mCache.put(url, bitmap);
			}

			public Bitmap getBitmap(String url) {
				return mCache.get(url);
			}
		});

		mImageLoader.get(url, listener);

		// 画像のサイズをここで固定
		imageView.setMinimumWidth(300);
		imageView.setMinimumHeight(200);
		imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);

		// クリックイベント
		imageView.setOnClickListener(new View.OnClickListener() {

					public String url;

					public View.OnClickListener setUrl(String url) {
						this.url = url;
						return this;
					}

					public void onClick(View v) {
						// イメージ画像がクリックされたときに実行される処理
						Log.d(TAG, "url= " + url);

						// 画面遷移
						Intent intent = new Intent(context, TSUGINO_GAMEN.class);
						intent.putExtra("photoUrlAddress", url);
						context.startActivity(intent);

					}
				}.setUrl(url)
		);

		return imageView;
	}


}

ここで、

// 画像のサイズをここで固定
imageView.setMinimumWidth(300);
imageView.setMinimumHeight(200);
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);

これが必要だった。setScaleTypeはおこのみで。

クリックイベントにも注目。

// クリックイベント
imageView.setOnClickListener(new View.OnClickListener() {

			public String url;

			public View.OnClickListener setUrl(String url) {
				this.url = url;
				return this;
			}

			public void onClick(View v) {
				// イメージ画像がクリックされたときに実行される処理
				Log.d(TAG, "url= " + url);

				// 画面遷移
				Intent intent = new Intent(context, TSUGINO_GAMEN.class);
				intent.putExtra("photoUrlAddress", url);
				context.startActivity(intent);

			}
		}.setUrl(url)
);

setUrlを使ってurlをイベントリスナー内に入れ込んでいる。イベントに値を渡す方法。少し凝ったことをしたいなら必要になってくるだろう。

BASIC認証は使わないだろうからはずしてもいいじゃろう。

// BASIC認証
mImageLoader = new BasicAuthImageLoader(mQueue, new ImageLoader.ImageCache() {
	private final LruCache&lt;String, Bitmap&gt; mCache = new LruCache&lt;String, Bitmap&gt;(100);

	public void putBitmap(String url, Bitmap bitmap) {
		mCache.put(url, bitmap);
	}

	public Bitmap getBitmap(String url) {
		return mCache.get(url);
	}
});

Activity、Intentのことについて(いろんなオプション)

こちらを参考にして、
intent.setFlagsメソッドの引数について

画面を遷移するときなどにこれを渡せばいいらしい。使い方は以下のような、

// 引数1:自身のActivity、引数2:移動先のActivity名
Intent intent = new Intent(MainActivity.this, SubActivity.class);
			
// オプションを指定
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
			
// Activityの移動
startActivity(intent);

あとタスクに関係するのであればマニフェストの、

android:launchMode=”singleTask”

なども考慮するといいかもしれない。

また一つ賢くなったね!!

アラートダイアログの外側を押せないようにする

アラートダイアログの外側を押せないようにする、ダイアログは便利ですが外側を押しても反応しちゃう。

そこで

	// ダイアログ表示
	public AlertDialog.Builder builder;

こんな感じで、アラートダイアログを宣言して

builder = new AlertDialog.Builder(this);
builder.setTitle("登録しました");

// 外側を押せないようにする
builder.setCancelable(false);

builder.setNegativeButton("OK", new DialogInterface.OnClickListener() {
	public void onClick(DialogInterface dialog, int which) {

		// 遷移
		Intent intent = new Intent(getApplicationContext(), hogehoge.class);
		startActivity(intent);

	}
});

// 表示
builder.create().show();

こうすればいいですね。

builder.create().show(); の create() はわけわかめ。なんだろうか、こいつは。

自分的Volleyの定番コード

自分的Volleyの定番コード

CustomRequestクラスを作成して以下の文章でvolley使う。例文はPOSTを使用する場合です。

		HashMap<String, String> postMap = new HashMap<String, String>();

		postMap.put("uuid", uuId);

		RequestQueue requestQueue = Volley.newRequestQueue(this);
		CustomRequest jsObjRequest = new CustomRequest(Request.Method.POST, Constant.USER_ADD_URL, postMap,
				new Response.Listener<JSONObject>() {
					@Override
					public void onResponse(JSONObject response) {

						Log.v("初期ユーザー登録、JSON", response.toString());

						// ここでタイマー起動
						// 3秒カウント開始のスケジュール
						timer = new Timer();
						timer.schedule(new MyTimer(), Constant.TITLE_COUNT_SEC);

					}
				},
				new Response.ErrorListener() {
					@Override
					public void onErrorResponse(VolleyError error) {

						Log.v("初期ユーザー登録、VolleyErr", "" + error);

					}
				});
		requestQueue.add(jsObjRequest);
public class CustomRequest extends Request<JSONObject> {

	private Response.Listener<JSONObject> listener;
	private Map<String, String> params;

	public CustomRequest(String url, Map<String, String> params,
						 Response.Listener<JSONObject> reponseListener, Response.ErrorListener errorListener) {
		super(Request.Method.GET, url, errorListener);
		this.listener = reponseListener;
		this.params = params;
	}

	public CustomRequest(int method, String url, Map<String, String> params,
						 Response.Listener<JSONObject> reponseListener, Response.ErrorListener errorListener) {
		super(method, url, errorListener);
		this.listener = reponseListener;
		this.params = params;
	}

	protected Map<String, String> getParams()
			throws com.android.volley.AuthFailureError {
		return params;
	}

	;

	@Override
	protected Response<JSONObject> parseNetworkResponse(NetworkResponse response) {
		try {
			String jsonString = new String(response.data,
					HttpHeaderParser.parseCharset(response.headers));
			return Response.success(new JSONObject(jsonString),
					HttpHeaderParser.parseCacheHeaders(response));
		} catch (UnsupportedEncodingException e) {
			return Response.error(new ParseError(e));
		} catch (JSONException je) {
			return Response.error(new ParseError(je));
		}
	}

	@Override
	protected void deliverResponse(JSONObject response) {
		// TODO Auto-generated method stub
		listener.onResponse(response);
	}
}

イテレーターを使わないとJSONが解析できないことに気がついた

JSONでキーがあらかじめわかっていれば、使うのは簡単だった。

でもキーがわからないときはどうすればいいのか?

以下のとおり。

            JSONObject menuJson = new JSONObject(MENU_JSON);
            Iterator<?> keys = menuJson.keys();

            Log.d(TAG, "keys= " + keys.hasNext());

            while (keys.hasNext()) {
                String key = (String) keys.next();
                Log.d(TAG, "" + key);

                JSONObject j = new JSONObject(menuJson.getString(key));
                Iterator<?> keyss = j.keys();

                while (keyss.hasNext()) {

                    String keyy = (String) keyss.next();
                    Log.d(TAG, "" + keyy);

                }

            }

下がミソじゃの

while (keys.hasNext()) {
                String key = (String) keys.next();
}

Googleスプレッドシートを利用したGASのAPIを誰でも使えるようにする

Googleスプレッドシートを利用したGASのAPIを誰でも使えるようにしたかったんだけど、なかなか難しい。

解決策として、スプレッドシート内で作成したものを公開するのではなく、

Googleドライブ上に

スクリーンショット 2016-03-09 18.11.37

みたいな感じでスクリプトファイル自体を作成できるようにして、そこで新たにスクリプトを書く。

そのスクリプトをウェブアプリケーションとして公開!とやると、簡単に行けた。

どこからでもアクセスできるAPIができました。

Googleスプレッドシートは、スマホからでも簡単に更新できるため、かなりの利用幅が効くようになると思います。

Javaでマッチング、正規表現のとき、$1 $2 とか使いたいじゃん

Pattern p = Pattern.compile("^(.*?)(.png)$");
Matcher m = p.matcher(photoUrl);
String result = m.replaceFirst("$1"); 

こうすると result に$1が入るよ。つまり、

^(.*?)(.png)$

の最初の括弧の

(.*?)

にマッチした部分がゲットできるってことね。

photoUrl = “hogehoge.png”;

だったら、

hogehoge

がとれるってことだよん。

google スプレッドシートを利用してJSONをAPIで発行

基本はJavascript。

doOutput(e)関数で吐き出すらしい。これをAPIにすると、超便利である。

まず、
var summaryData = sh.getDataRange().getValues();
がミソ。これで一度すべてのシート内容を読む。シートが巨大な時はこれをつかう。早いから。

リターンで返す時は、
return ContentService.createTextOutput(JSON.stringify(json)).setMimeType(ContentService.MimeType.JSON);
などなど。単純に文字列を返してもだめっすよ、怒られます。

function doGet(e){ 
  var json = myFunction();
  Logger.log(JSON.stringify(json));
  return ContentService.createTextOutput(JSON.stringify(json)).setMimeType(ContentService.MimeType.JSON);
}

function doPost(e) {
  var json = myFunction();
  Logger.log(JSON.stringify(json));  
  return ContentService.createTextOutput(JSON.stringify(json)).setMimeType(ContentService.MimeType.JSON);
}

function myFunction() {
  
  var url = 'https://docs.google.com/spreadsheets/xxxxxxxxxxxxxxxxxxxxxxxxxx';
  var bk = SpreadsheetApp.openByUrl(url);
 var sh = bk.getSheetByName("food_drink");
  
  // 最後行を取得
  var lastRow = sh.getLastRow() - 1;
  // 最後列を取得
  var lastColumn = sh.getLastColumn();
  
  var list = [];
  var obj = {};
  var objShou = {};
  var objChuu = {};
  
 var summaryData = sh.getDataRange().getValues();
 
 Logger.log(lastRow);
  
  for (var y = 1; y <= lastRow; y++) {
  
    // 名前取得
  var obj1 = {};
  obj1.name = summaryData[y][4];
  obj1.money = summaryData[y][5];
  obj1.iconImageUrl = summaryData[y][6];
  obj1.photoImageUrl = summaryData[y][7];
  obj1.id = summaryData[y][0];
 
   list.push(obj1);
   
   // 小カテゴリー、次に変化があればオブジェクトへいれる
    if (y < lastRow) {
   
     var dy1 = summaryData[y][3];
     var dy2 = summaryData[y+1][3];
     if (dy1 != dy2) {
       objShou[dy1] = list;
       list = [];
     }
   
   } else {
   
     var dy1 = summaryData[y][3];
     objShou[dy1] = list;
     list = [];
   
   }
   
   // 中カテゴリー、次に変化があればオブジェクトへいれる
   if (y < lastRow) {
   
     var cy1 = summaryData[y][2];
     var cy2 = summaryData[y+1][2];
     if (cy1 != cy2) {
       objChuu[cy1] = objShou;
       objShou = {};
     }
   
   } else {
   
     var cy1 = summaryData[y][2];
       objChuu[cy1] = objShou;
       objShou = {};
   
   }
  
  }
  
  return objChuu;
  
}

よく使うタイマーの流れ

タイマーをよく使うのですが、備忘録として。

		// タイマー作動
		Timer timer1 = new Timer();
		Handler handler1 = new Handler();
		timer1.schedule(new DownloadTimer(), 0, 2000);

タイマー、ハンドラーを宣言。その後のスケジュールで0秒から開始、その後2000ミリ秒ごと(2秒)にタイマー作動。となっています。 new DownloadTimer() を宣言してそれを実行することになっています。

実際の処理内は、

	class DownloadTimer extends TimerTask {

		@Override
		public void run() {
			handler1.post(new Runnable(){
				@Override
				public void run() {


				}

			});

		}

	}

こんな感じでクラスをつくり public void run() { } に処理をかきます。それが実行されます。

ストレージに画像保存するためのクラス

public class ImageManager {

	private Context mContext;
	private String fileFullPath;

	public ImageManager(Context context) {
		mContext = context;
	}

	/**
	 * 画像の保存
	 *
	 * @param bitmap
	 * @param albumName
	 */
	public void save(Bitmap bitmap, String albumName) {
		if (!canUseSd()) {
			Log.e("Error", "Can't use SD Card");

			return;
		}

		saveToSd(getSdStorageDir(albumName), bitmap);
	}

	/**
	 * ストレージに画像保存
	 *
	 * @param dir
	 * @param bitmap
	 */
	private void saveToSd(File dir, Bitmap bitmap) {
		String fileName = getFileName();
		fileFullPath = dir.getAbsolutePath() + "/" + fileName;

		Log.d("保存パス","fileFullPath= " + fileFullPath);

		try {
			// 保存処理
			FileOutputStream fos = new FileOutputStream(fileFullPath);
			bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
			fos.flush();
			fos.close();
		} catch (Exception e) {
			Log.e("Error", "" + e.toString());
		} finally {
			// アルバムに反映
			addGallery(fileName);
		}
	}

	/**
	 * 保存した画像をギャラリーに追加
	 *
	 * @param fileName
	 */
	private void addGallery(String fileName) {
		try {
			ContentValues values = new ContentValues();
			ContentResolver contentResolver = mContext.getContentResolver();
			values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
			values.put(MediaStore.Images.Media.TITLE, fileName);
			values.put("_data", fileFullPath);
			contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
		} catch (Exception e) {
			Log.e("Error", "" + e);
		}
	}

	/**
	 * 画像のファイル名を日付から生成し取得
	 *
	 * @return
	 */
	private String getFileName() {
		Date mDate = new Date();
		SimpleDateFormat fileNameFormat = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.ENGLISH);
		String fileName = fileNameFormat.format(mDate) + ".jpg";

		return fileName;
	}

	/**
	 * ストレージのストレージパス取得
	 *
	 * @param albumName
	 * @return
	 */
	private File getSdStorageDir(String albumName) {
		File dir = new File(Environment.getExternalStoragePublicDirectory(
				Environment.DIRECTORY_PICTURES), albumName);

		if (!dir.exists()) {
			if (!dir.mkdirs()) {
				Log.e("Error", "Directory not created");
			}
		}

		return dir;
	}

	/**
	 * ストレージが読み込み可能か
	 *
	 * @return
	 */
	public boolean canReadSd() {
		String status = Environment.getExternalStorageState();
		if (!status.equals(Environment.MEDIA_MOUNTED)) {
			return false;
		}

		File file = Environment.getExternalStorageDirectory();
		if (file.canRead()) {
			return true;
		}

		return false;
	}

	/**
	 * ストレージに書き込み可能か
	 *
	 * @return
	 */
	public boolean canWriteSd() {
		String status = Environment.getExternalStorageState();
		if (!status.equals(Environment.MEDIA_MOUNTED)) {
			return false;
		}

		File file = Environment.getExternalStorageDirectory();
		if (file.canWrite()) {
			return true;
		}

		return false;
	}

	/**
	 * ストレージが使用可能か
	 *
	 * @return
	 */
	public boolean canUseSd() {
		return canReadSd() && canWriteSd();
	}
}

使い方は、

			ImageView imageView = (ImageView) findViewById(R.id.photo_image);
			Bitmap imageForScale = ((BitmapDrawable) imageView.getDrawable()).getBitmap();

			ImageManager imageManager = new ImageManager(this);
			try {
				String albumName = "Save image sample";
				imageManager.save(imageForScale, albumName);
			} catch (Error e) {
				Log.e("MainActivity", "onCreate: " + e);
			}